/*
	File:    	AppleCarPlay_AppStub.c
	Package: 	Apple CarPlay Communication Plug-in.
	Abstract: 	n/a
	Version: 	n/a

	Disclaimer: IMPORTANT: This Apple software is supplied to you, by Apple Inc. ("Apple"), in your
	capacity as a current, and in good standing, Licensee in the MFi Licensing Program. Use of this
	Apple software is governed by and subject to the terms and conditions of your MFi License,
	including, but not limited to, the restrictions specified in the provision entitled ”Public
	Software”, and is further subject to your agreement to the following additional terms, and your
	agreement that the use, installation, modification or redistribution of this Apple software
	constitutes acceptance of these additional terms. If you do not agree with these additional terms,
	please do not use, install, modify or redistribute this Apple software.

	Subject to all of these terms and in consideration of your agreement to abide by them, Apple grants
	you, for as long as you are a current and in good-standing MFi Licensee, a personal, non-exclusive
	license, under Apple's copyrights in this original Apple software (the "Apple Software"), to use,
	reproduce, and modify the Apple Software in source form, and to use, reproduce, modify, and
	redistribute the Apple Software, with or without modifications, in binary form. While you may not
	redistribute the Apple Software in source form, should you redistribute the Apple Software in binary
	form, you must retain this notice and the following text and disclaimers in all such redistributions
	of the Apple Software. Neither the name, trademarks, service marks, or logos of Apple Inc. may be
	used to endorse or promote products derived from the Apple Software without specific prior written
	permission from Apple. Except as expressly stated in this notice, no other rights or licenses,
	express or implied, are granted by Apple herein, including but not limited to any patent rights that
	may be infringed by your derivative works or by other works in which the Apple Software may be
	incorporated.

	Unless you explicitly state otherwise, if you provide any ideas, suggestions, recommendations, bug
	fixes or enhancements to Apple in connection with this software (“Feedback”), you hereby grant to
	Apple a non-exclusive, fully paid-up, perpetual, irrevocable, worldwide license to make, use,
	reproduce, incorporate, modify, display, perform, sell, make or have made derivative works of,
	distribute (directly or indirectly) and sublicense, such Feedback in connection with Apple products
	and services. Providing this Feedback is voluntary, but if you do provide Feedback to Apple, you
	acknowledge and agree that Apple may exercise the license granted above without the payment of
	royalties or further consideration to Participant.

	The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO WARRANTIES, EXPRESS OR
	IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY
	AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR
	IN COMBINATION WITH YOUR PRODUCTS.

	IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES
	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
	PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION
	AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF CONTRACT, TORT
	(INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE
	POSSIBILITY OF SUCH DAMAGE.

	Copyright (C) 2007-2021 Apple Inc. All Rights Reserved. Not to be used or disclosed without permission from Apple.
*/

//===========================================================================================================================
//===========================================================================================================================

#include <stdlib.h>
#include <math.h>
#include <signal.h>
#include <pthread.h>
#include <time.h>
#include "AppleCarPlay_AppStub.h"
#include "StringUtils.h"
#include "AirPlayReceiverServer.h"
#include "AirPlayReceiverSession.h"
#include "AirPlayVersion.h"
#include "AirPlayUtils.h"
#include "DebugServices.h"
#include "HIDKnob.h"
#include "HIDTouchScreen.h"
#include "HIDProximity.h"
#include "HIDTelephony.h"
#include "HIDMediaButtons.h"
#include "MathUtils.h"
#include "AudioUtils.h"
#include "CarPlayLanguages.h"
#include "CarPlayControlClient.h"
#include "BonjourBrowser.h"
#include "BonjourBrowser.h"
#include "iAP2_AppStub.h"
#include <linux/types.h>

//===========================================================================================================================
//	Internals
//===========================================================================================================================

#define kDefaultUUID			CFSTR( "3a07bd78" )
//#define kMainScreenUUID			CFSTR( "e5f7a68d-7b0f-4305-984b-974f677a150b" )
#define kMainScreenUUID			CFSTR( "6b8b4567327b23c6-643c9869-4873-af1b-19495cff2ae8944a625558ec" )
#define kAltScreenUUID			CFSTR( "e5f7a68d-7b0f-4305-984b-b051a776f479" )
#define kAltScreen2UUID			CFSTR( "e5f7a68d-7b0f-4305-984b-b051a776f472" )
#define kScreenNoTimeout		( (uint64_t) INT64_C( -1 ) )

#define kHsaeVendorId						0x11c0
#define kHsaeProductId						0x0001

#define kUSBCountryCodeUnused               0
#define kUSBCountryCodeUS                   33
#define kUSBVendorTouchScreen               kHsaeVendorId
#define kUSBProductTouchScreen              kHsaeProductId
#define kUSBVendorKnobButtons               0
#define kUSBProductKnobButtons              0
#define kUSBVendorProxSensor                0
#define kUSBProductProxSensor               0

#define kUSBVendorTelephony                 kHsaeVendorId
#define kUSBProductTelephony                kHsaeProductId
#define kUSBVendorMediaButtons              kHsaeVendorId
#define kUSBProductMediaButtons             kHsaeProductId

// Prototypes
static void aprintBuffer(char buf[], int length,char * tag);
static OSStatus	_SetupUI( void );
static void		_TearDownUI( void );
static void		_RunUI( void );

static void *	_AirPlayThread( void *inArg );

static void 	_CarPlayControlClientEventCallback( CarPlayControlClientRef client, CarPlayControlClientEvent event, void *eventInfo, void *context );

static CFTypeRef
	_AirPlayHandleServerCopyProperty(
		AirPlayReceiverServerRef	inServer,
		CFStringRef					inProperty,
		CFTypeRef					inQualifier,
		OSStatus *					outErr,
		void *						inContext );

static void
	_AirPlayHandleSessionCreated(
		AirPlayReceiverServerRef	inServer,
		AirPlayReceiverSessionRef	inSession,
		void *						inContext );

static void
	_AirPlayHandleCtrlConnectRequest(
		AirPlayReceiverServerRef	inServer,
		AirPlayReceiverSessionRef	inSession,
		uint64_t					receiverDeviceID,
		void *						inContext );

static void
	_AirPlayHandleAuthSetupRequest(
		AirPlayReceiverServerRef	inServer,
		AirPlayReceiverSessionRef	inSession,
		void *						inContext );

static void
	_AirPlayHandleSessionFinalized(
		AirPlayReceiverSessionRef inSession,
		void *inContext );

static void
	_AirPlayHandleSessionStarted(
		AirPlayReceiverSessionRef inSession,
		void *inContext );

static void
	_AirPlayHandleModesChanged(
		AirPlayReceiverSessionRef 	inSession,
		const AirPlayModeState *	inState,
		void *						inContext );

static void
	_AirPlayHandleInitialModes(
		AirPlayReceiverSessionRef 	inSession,
		CFDictionaryRef				inParams,
		void *						inContext );

static void
	_AirPlayHandleChangeModes(
		AirPlayReceiverSessionRef 	inSession,
		CFDictionaryRef				inParams,
		void *						inContext );

static AirPlayModeChanges *
	_AirPlayHandleGetModesChanged(
		AirPlayReceiverSessionRef 	inSession,
		void *						inContext );

static AirPlayModeChanges *
	_AirPlayHandleGetAirPlayModeState(
		AirPlayReceiverSessionRef 	inSession,
		void *						inContext );

static void
	_AirPlayHandleGetUsbVidPid(
		AirPlayReceiverSessionRef 	inSession,
		void *						inVid,
		void *						inPid);

static void *
	_AirPlayHandleGetAudioFormat(
		AirPlayReceiverSessionRef 	inSession,
		uint8_t						audioType);

static void *
	_AirPlayHandleGetDisplayInfo(
			AirPlayReceiverSessionRef 	inSession,
			int *                       width,
			int *                       height,
			int *                       viewAreaWidth,
			int *                       viewAreaHeight,
			int *                       maxFPS,
			void *						inContext );

static void
	_AirPlayHandleRequestUI(
		AirPlayReceiverSessionRef inSession,
		CFStringRef inURL,
		void *inContext );

static void
	_AirPlayHandleRequestSiri(
		AirPlayReceiverSessionRef inSession,
		void *inContext );

static CFTypeRef
	_AirPlayHandleSessionCopyProperty(
		AirPlayReceiverSessionRef	inSession,
		CFStringRef					inProperty,
		CFTypeRef					inQualifier,
		OSStatus *					outErr,
		void *						inContext );

static OSStatus
	_AirPlayHandleSessionControl(
		AirPlayReceiverSessionRef 	inSession,
 		CFStringRef         		inCommand,
	    CFTypeRef           		inQualifier,
	    CFDictionaryRef     		inParams,
		CFDictionaryRef *   		outParams,
		void *				        inContext );

static OSStatus
	_AirPlayHandleSessionDisplay(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		inParams,
		void *						inContext );

static OSStatus
	_AirPlayHandleAudioFocusChanged(
		int 						audioType,
        int     					focus );

static OSStatus
	_AirPlayHandleSessionInfo(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		inParams,
		void *						inContext );

static OSStatus
	_AirPlayHandleSessionHIDReport(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		inParams,
		void *						inContext );

static OSStatus
	_AirPlayHandleSessionAudioFormatReport(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		dict,
		void *						inContext );

static OSStatus
	_AirPlayHandleSessionAudioLatencyReport(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		dict,
		void *						inContext );

static void
	_AirPlayHandleSetEnhancedSiriParams(
		AirPlayReceiverSessionRef	inSession,
		AirPlayVoiceActivationMode	inMode,
		CFStringRef			inLanguage,
		void *				inContext );

static void
	_AirPlayHandleSuggestUI(
		AirPlayReceiverSessionRef 	inSession,
		CFArrayRef			inURLs,
		void *				inContext );

static void
    _AirPlayHandleDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    double						inVolume,
	    void *						inContext );

static void
    _AirPlayHandleunDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    void *						inContext );

static void
    _AirPlayHandleSessionScreenStart( );

static void
	_AirPlayHandleSessionScreenStop( );

static void
    _AirPlayHandleSessionForceKeyFrame( );

static void
	_AirPlayHandleSessionSetNightMode(uint8_t mode);

static void
	_AirPlayHandleSessionUpdateViewAreaIndex(uint8_t index);

static void
    _AirPlayHandleSessionAudioStreamStart(
    		const char* audioType,
            int handle,
            CarplayAudioStreamType streamType,
            int rate,
            int bits,
            int channels,
            void* inStream);

static void
    _AirPlayHandleSessionAudioStreamStop(int handle, CarplayAudioStreamType streamType);

static OSStatus	_ParseModes( const char *inArg );
static OSStatus	_ParseModeResource( AirPlayResourceChange *inResource, const char *inArg );

static void _initialModesCommand( const char * inCommand );

static void
	_sendGenericChangeModeRequest(
		const char *				inScreenBorrowID,
		AirPlayTransferType 		inScreenType,
		AirPlayTransferPriority		inScreenPriority,
		AirPlayConstraint			inScreenTake,
		AirPlayConstraint			inScreenBorrow,
		const char *				inAudioBorrowID,
		AirPlayTransferType 		inAudioType,
		AirPlayTransferPriority		inAudioPriority,
		AirPlayConstraint			inAudioTake,
		AirPlayConstraint			inAudioBorrow,
		AirPlayTriState				inPhone,
		AirPlaySpeechMode			inSpeech,
		AirPlayTriState				inTurnByTurn );

static void
	_sendInitialChangeModeRequest(
		const char *				inScreenBorrowID,
		AirPlayTransferType 		inScreenType,
		AirPlayTransferPriority		inScreenPriority,
		AirPlayConstraint			inScreenTake,
		AirPlayConstraint			inScreenBorrow,
		const char *				inAudioBorrowID,
		AirPlayTransferType 		inAudioType,
		AirPlayTransferPriority		inAudioPriority,
		AirPlayConstraint			inAudioTake,
		AirPlayConstraint			inAudioBorrow,
		AirPlayTriState				inPhone,
		AirPlaySpeechMode			inSpeech,
		AirPlayTriState				inTurnByTurn,
		AirPlayEntity				inScreenPermanentEntity,
		AirPlayConstraint			inScreenPermanentTakeConstraint,
		AirPlayConstraint			inScreenPermanentBorrowConstraint,
		AirPlayEntity				inAudioPermanentEntity,
		AirPlayConstraint			inAudioPermanentTakeConstraint,
		AirPlayConstraint			inAudioPermanentBorrowConstraint );

static void
	_setChangeModesStruct(
		AirPlayModeChanges *		outModeChanges,
		const char *				inScreenBorrowID,
		AirPlayTransferType 		inScreenType,
		AirPlayTransferPriority		inScreenPriority,
		AirPlayConstraint			inScreenTake,
		AirPlayConstraint			inScreenBorrow,
		const char *				inAudioBorrowID,
		AirPlayTransferType 		inAudioType,
		AirPlayTransferPriority		inAudioPriority,
		AirPlayConstraint			inAudioTake,
		AirPlayConstraint			inAudioBorrow,
		AirPlayTriState				inPhone,
		AirPlaySpeechMode			inSpeech,
		AirPlayTriState				inTurnByTurn );

static void
	_changeResourceMode(
		const char *				inBorrowID,
		AirPlayResourceID			inResourceID,
		AirPlayTransferType			inType,
		AirPlayTransferPriority		inPriority,
		AirPlayConstraint			inTakeConstraint,
		AirPlayConstraint			inBorrowOrUnborrowConstraint);

static void
	_changeAppState(
		AirPlaySpeechMode			inSpeechMode,
		AirPlayTriState				inPhoneCall,
		AirPlayTriState				inTurnByTurn);

static void _requestUI();
static void _requestUIWithURL( const char * url );
static void _requestSiriAction( AirPlaySiriAction	inAction );
static void _setNightMode( int mode );
static void _UIAppearanceUpdate( int inMode, int inSetting );
static void _setLimitedUI( int inLimitUI );
static void _AirPlayReceiverSessionEnd();
static void _CarPlayControllerConnect();
static int _CarPlayControllerContainsCString( const char * inDeviceID );
static int _CarPlayAccessoryWrite(int handle,signed char * data, signed short len,int *length);
static int _CarPlayAccessoryRead(int handle,signed char * data, signed short len,int *length);
static int	_CarPlayControllerEqual();
static void _AirPlayReceiverSessionSendiAPMessage( int length, const unsigned char *ptr );

static CFArrayRef _getAudioFormats( OSStatus *outErr );
static CFArrayRef _getAudioLatencies( OSStatus *outErr );
static OSStatus _setupHIDDevices( void );
static CFArrayRef _getHIDDevices( OSStatus *outErr );
static CFArrayRef _getScreenDisplays( OSStatus *outErr );
static CFArrayRef _getScreenDisplaysWindowConfig( OSStatus *outErr );
static OSStatus _sendETCUpdate(bool inETCEnabled);

static CFDictionaryRef createInitialModesForBackupCamera( void );
static CFDictionaryRef createInitialModesForController( void );

ulog_define( CarPlayDemoApp, kLogLevelTrace, kLogFlags_Default, "CarPlayDemoApp", NULL );
#define app_ucat()					&log_category_from_name( CarPlayDemoApp )
#define app_ulog( LEVEL, ... )		ulog( app_ucat(), (LEVEL), __VA_ARGS__ )
#define app_dlog( LEVEL, ... )		dlogc( app_ucat(), (LEVEL), __VA_ARGS__ )

ulog_define( CarPlayDemoAppErr, kLogLevelTrace, kLogFlags_Default | kLogFlags_PrintError, "CarPlayDemoAppErr", NULL );
#define app_ucat_err()					&log_category_from_name( CarPlayDemoAppErr )
#define app_ulog_err( LEVEL, ... )		ulog( app_ucat_err(), (LEVEL), __VA_ARGS__ )

// Globals

static bool							gKnob			= false;
static bool							gHiFiTouch		= true;
static bool							gLoFiTouch		= false;
static bool							gTouchpad		= false;
static bool							gProxSensor		= false;
static bool							gTelephony		= true;
static bool                         gMediaButtons   = true;
static int							gPrimaryInputDevice			= kAirPlayDisplayPrimaryInputDeviceTouch;
static bool							gEnhancedRequestCarUI		= false;
static bool							gETCSupported				= false;
static AirPlayReceiverServerRef		gAirPlayServer	= NULL;
static AirPlayReceiverSessionRef	gAirPlaySession	= NULL;
static CarPlayControlClientRef		gCarPlayControlClient = NULL;
static CFMutableArrayRef			gCarPlayControllers = NULL;
static pthread_t					gAirPlayThread = 0;
static AirPlayModeChanges			gInitialModeChangesRaw;
static AirPlayModeState 			gAirPlayModeStateRaw; //所有焦点状态保存在这里
static AirPlayInitialPermanentEntity	gInitialPermanentEntity;
static CFDictionaryRef				gInitialModes		= NULL;
static bool							gHasInitialModes	= false;
static CFMutableArrayRef			gBluetoothIDs				= NULL;
static CFBooleanRef					gRightHandDrive				= NULL;
static CFBooleanRef					gNightMode				= NULL;
static CFBooleanRef					gLimitedUI				= NULL;
static AirPlayAppearanceMode		gAppearanceMode			= kAirPlayAppearanceMode_Light;
static AirPlayAppearanceSetting		gAppearanceSetting		= kAirPlayAppearanceSetting_Automatic;
static CFStringRef					gDeviceID				= NULL;
static CFStringRef					giOSVersionMin			= NULL;

static uint8_t						gCStringTouchUID[200] = { 0 };
static uint8_t						gCStringTouchUID2[200]= { 0 };
static uint8_t						gCStringTouchPadUID[200];
static uint8_t						gCStringTouchPadUID2[200];
static uint8_t						gCStringKnobUID[200];
static uint8_t						gCStringProxSensorUID[200];
static uint8_t						gCStringTelephonyUID[200];
static uint8_t                      gCStringMediaButtonsUID[200];
static uint8_t                      gCStringMediaButtonsUID1[200];

static uint8_t						gTouchUID;
static uint8_t						gKnobUID;
static uint8_t						gProxSensorUID;
static uint8_t						gTelephonyUID;
static uint8_t                      gMediaButtonsUID;
static uint8_t						gNextDeviceUID = 0;
static int							gRawVideoWidth	= 800;
static int							gRawVideoHeight	= 480;
static int							gVideoWidth		= 800;
static int							gVideoHeight	= 480;
static int							gViewAreaWidth		= 800;
static int							gViewAreaHeight	    = 480;
static int							gTouchFinger	= 1;
static int							gOriginalSafeWidth		= 800;
static int							gOriginalSafeHeight	    = 480;
static int							gOriginalX		= 0;
static int							gOriginalY	    = 0;
static int							gWidthPhysical;
static int							gHeightPhysical;
static int							gWidthLogical = 0;
static int							gHeightLogical = 0;
static int							gTouchPadWidthLogical = 0;
static int							gTouchPadHeightLogical = 0;
static int							gMaxFPS;
static int							gHidDriverFd = -1;
static int							touchPadUserId = 1;
static int							mediaButtonUserId = 2;
static int							mediaButtonIAP2UserId = 9;
static int							mediaButtonUserId2 = 6;
static int							telephoneButtonUserId = 3;
static int							knobButtonUserId = 4;
static int							telephoneButtonCycle = 0;
static int					        HIDYarisCarModel = 0 ; //Yaris Cross
static int							xqcarplayDebugTouch = 0;
static int							xqcarplayFirstTime = 1;
static int							ofnInputSupport = 0; //方控触摸
static int							ofnInputStartTime = 0; //方控触摸
static int							audiQ3InputStartTime = 0; //奥迪Q3S触摸
static int							ofnInputFlipCount = 0; //方控触摸
static int							borrowScreenCount = 0; //屏幕所有权借用次数
static const uint8 iAP2CheckBuf[]       = { 0xFF, 0x55, 0x02, 0x00, 0xEE, 0x10 };
// AudioLatencies
static CFDictionaryRef				gAltAudioLatency		= NULL;
static CFDictionaryRef				gTelephoneAudioLatency	= NULL;
static CFDictionaryRef				gSpeechAudioLatency	    = NULL;
static CFDictionaryRef				gMediaAudioLatency	    = NULL;
//MediaAudioFormat
static int                          mediaLatencySampleRate = 48000;
static int                          mediaLatencyAudioLatencyMicros = 50000;
//TelephoneAudioFormat
static int                          phoneLatencySampleRate = 16000;
static int                          phoneLatencyAudioLatencyMicros = 50000;
//SpeechAudioFormat
static int                          speechLatencySampleRate = 16000;
static int                          speechLatencyAudioLatencyMicros = 50000;
//AlertAudioFormat
static int                          alertLatencySampleRate = 48000;
static int                          alertLatencyAudioLatencyMicros = 50000;
// AudioFormats
static CFDictionaryRef				gAltAudioFormats		= NULL;
static CFDictionaryRef				gTelephoneAudioFormats	= NULL;
static CFDictionaryRef				gSpeechAudioFormats	    = NULL;
static CFDictionaryRef				gMediaAudioFormats	    = NULL;
//MediaAudioFormat
static int                          mediaSampleRate = 48000;
static int                          mediaBits = 16;
static int                          mediaChannels = 2;
//TelephoneAudioFormat
static int                          phoneSampleRate = 16000;
static int                          phoneBits = 16;
static int                          phoneChannels = 1;
//SpeechAudioFormat
static int                          speechSampleRate = 16000;
static int                          speechBits = 16;
static int                          speechChannels = 1;
//AlertAudioFormat
static int                          alertSampleRate = 48000;
static int                          alertBits = 16;
static int                          alertChannels = 2;

#define HID_REGISTER	0x12703
#define HID_UNREGISTER	0x12704
#define HID_SET_DATA	0x12705

struct hid_user_register_data32 {
	__u32  report_desc;
	__u32 report_desc_len;
	__u32 user_id;
};

struct hid_user_data32 {
	__u32  data;
	__u32 data_len;
	__u32 user_id;
};

struct hid_user_unregister_data {
	__u32 remove_all;
	__u32 user_id;
};


pthread_mutex_t						authMutex = PTHREAD_MUTEX_INITIALIZER;
char								gPairingIdentity[ 128 ];

// Current state of the virtual knob
Boolean gSelectButtonPressed = false;
Boolean gHomeButtonPressed = false;
Boolean gBackButtonPressed = false;
Boolean gSiriButtonPressed = false;
double	gXPosition = 0;
double	gYPosition = 0;
int8_t	gWheelPositionRelative = 0;
// Maximum and minimum X values reported by actual knob
#define kMaxXPosition	(1.0)
#define kMinXPosition	(-1.0)
// Maximum and minimum X values reported by actual knob
#define kMaxYPosition	(1.0)
#define kMinYPosition	(-1.0)
OSStatus	KnobUpdate( void );

static OSStatus _touchScreenUpdate( uint8_t inPress, uint16_t inX, uint16_t inY );
static OSStatus _telephonyUpdate( int inButtonIndex );
static OSStatus _mediaButtonsUpdate( int inButtonIndex );
static OSStatus _proxSensorUpdate( bool inProxSensorPresence );

static Boolean gSupportsEnhancedSiri = false;
static Boolean gSupportsViewAreas = false;
static Boolean gSupportsUIContext = false;
static Boolean gSupportsAltScreen = false;
static Boolean gSupportsCornerMasks = false;
static Boolean gSupportsFocusTransfer = false;
static CarPlayAppStubDelegate    gAppStubDelegate;
static int     gAppNeedReleaseScreen = 0; //在电话，或者语音之前是否界面可见，1代表电话挂断后，需要归还屏幕控制权给车机
static dispatch_queue_t  eventQueue;
static dispatch_source_t source;
static dispatch_source_t mediaButtonSource;
static uint8_t * videoKeyFrame = NULL;
static uint64_t  videoKeyFrameLen = 0;

//===========================================================================================================================
//	main
//===========================================================================================================================

//int	main( int argc, char **argv )
int     StartAppleCarPlay( int argc, char **argv )
{
	app_ulog( kLogLevelNotice, "AppStub-StartAppleCarPlay 1046.\n" );
	uint8_t		outMACAddress[ 6 ];
	char		MACAddressCString[ 20 ] = { 0 };
	GetInterfaceMACAddress( "usb0", outMACAddress );
	MACAddressToCString( outMACAddress, MACAddressCString );
	app_ulog( kLogLevelNotice, "Tinker AppStub-StartAppleCarPlay usb0 MAC: %s \n", MACAddressCString);
	if(strlen( MACAddressCString )>0)
	{
		__system_property_set("sys.shark.interface.mac-address", MACAddressCString);
//		system("ip -6 route del fe80::/64 dev usb0");
//		system("ip -6 route add fe80::/64 dev usb0");
	}
	char	debugTouch [ 10 ];
	// __system_property_get("persist.xqcarplay.debug.touch",debugTouch);
	if( strlen(debugTouch) > 0 )
	{
		app_ulog( kLogLevelNotice, "AirPlay starting debugTouch %s\n", debugTouch);
		xqcarplayDebugTouch = atoi( debugTouch );
	}

	int					i;
	const char *		arg;
	OSStatus			err;
	app_ulog( kLogLevelNotice, "AirPlay starting version %s\n", kAirPlaySourceVersionStr);
	signal( SIGPIPE, SIG_IGN ); // Ignore SIGPIPE signals so we get EPIPE errors from APIs instead of a signal.
	AirPlayModeChangesInit( &gInitialModeChangesRaw );
	AirPlayInitialPermanentEntityInit( &gInitialPermanentEntity );

	gAirPlayModeStateRaw.initalScreenAccessory = 0; //当前屏幕焦点不处于车机

	if( gCarPlayControllers )
	{
		CFIndex count;
		count = CFArrayGetCount( gCarPlayControllers );
		if( count > 0 )	CFArrayRemoveAllValues( gCarPlayControllers );
	}

	if( gHasInitialModes )
	{
		gInitialModes = AirPlayCreateModesDictionary( &gInitialModeChangesRaw, NULL, &err );
		check_noerr( err );
	}
	eventQueue = dispatch_queue_create( "AppStubEventQueue", NULL );
	// Set up interaction with the platform UI framework.

	err = _SetupUI();
	require_noerr( err, exit );

	err = _setupHIDDevices();
	require_noerr( err, exit );
#if 0
	// Start AirPlay in a separate thread since the demo app needs to own the main thread.
	app_ulog( kLogLevelNotice, "AppStub-StartCarplay Create AirPlayThread.\n" );
	err = pthread_create( &gAirPlayThread, NULL, _AirPlayThread, NULL );
	require_noerr( err, exit );
#endif
	// Run the main loop for the app to receive UI events. This doesn't return until the app quits.

	_RunUI();

exit:
	_TearDownUI();
	return( err ? 1 : 0 );
}

int	StartCarPlayNCM()
{
	OSStatus			err;

	uint8_t		outMACAddress[ 6 ];
	char		MACAddressCString[ 20 ] = { 0 };
	GetInterfaceMACAddress( "usb0", outMACAddress );
	MACAddressToCString( outMACAddress, MACAddressCString );
	app_ulog( kLogLevelNotice, "Tinker AppStub-StartCarPlayNCM usb0 MAC: %s \n", MACAddressCString);
	if(strlen( MACAddressCString )>0)
	{
		__system_property_set("sys.shark.interface.mac-address", MACAddressCString);
//		system("ip -6 route del fe80::/64 dev usb0");
//		system("ip -6 route add fe80::/64 dev usb0");
	}

#if 1
	// Start AirPlay in a separate thread since the demo app needs to own the main thread.
	app_ulog( kLogLevelNotice, "AppStub-StartCarPlayNCM Create AirPlayThread.\n" );
	err = pthread_create( &gAirPlayThread, NULL, _AirPlayThread, NULL );
//	require_noerr( err, exit );
#endif

	return 0;
}
//===========================================================================================================================
//	_StartAirPlayServer
//===========================================================================================================================

static OSStatus	_StartAirPlayServer()
{
	OSStatus			err = kNoErr;

    app_ulog( kLogLevelNotice, "_StartAirPlayServer comming.\n" );

	if( gAirPlayThread )
	{
		app_ulog_err( kLogLevelNotice, "AirPlay has been already initialized.\n" );
		return kAlreadyInitializedErr;
	}

	// Start AirPlay in a separate thread since the demo app needs to own the main thread.

	err = pthread_create( &gAirPlayThread, NULL, _AirPlayThread, NULL );
	require_noerr( err, exit );

exit:
	if( err )	app_ulog_err( kLogLevelNotice, "Start AirPlay in a separate thread err.\n" );
	return( err );
}

//===========================================================================================================================
//	_StopAirPlayServer
//===========================================================================================================================

static void	_StopAirPlayServer()
{
	if( !gAirPlayThread )
	{
		app_ulog_err( kLogLevelNotice, "AirPlay has been already stopped err.\n" );
		return;
	}
	CFRunLoopStop( CFRunLoopGetCurrent() );
	gAirPlayThread = 0;

	app_ulog_err( kLogLevelNotice, "AirPlay is stopped successfully.\n" );
}


//===========================================================================================================================
//	PublishSharkBonjour
//===========================================================================================================================

static void	_PublishSharkBonjour()
{
	//Only the iAP2 is authenticated go here
	app_ulog( kLogLevelNotice, "_PublishSharkBonjour.\n");
	// 一定要鉴权过后才发布
//	if( gAirPlayServer != NULL )
//	{
//		AirPlayReceiverServerStart( gAirPlayServer ); //刷新一下Bonjour
//	}
	app_ulog( kLogLevelNotice, "_PublishSharkBonjour completed.\n");

}

//===========================================================================================================================
//	_IdentificationInfo
//===========================================================================================================================

static void	_IdentificationInfo()
{
	//Only the iAP2 is authenticated go here
	app_ulog( kLogLevelNotice, "_IdentificationInfo.\n");
	if(gAppStubDelegate.airPlayRequestNetworkCard_f != NULL){
		gAppStubDelegate.airPlayRequestNetworkCard_f(0, 1);
	}
	app_ulog( kLogLevelNotice, "_IdentificationInfo completed.\n");
}
//===========================================================================================================================
//	_RequestSharkDeviceId
//===========================================================================================================================

static void	_RequestSharkDeviceId(uint8 *data, uint16 len)
{
	uint8_t		outMACAddress[ 6 ];
	char		MACAddressCString[ 20 ] = { 0 };
	GetInterfaceMACAddress( "usb0", outMACAddress );
	MACAddressToCString( outMACAddress, MACAddressCString );
	app_ulog( kLogLevelNotice, "_RequestSharkDeviceId usb0 MAC: %s \n", MACAddressCString);
	if(strlen( MACAddressCString )>0)
	{
		// __system_property_set("sys.shark.interface.mac-address", MACAddressCString);
		memcpy(data,MACAddressCString,len);
	}

}
//===========================================================================================================================
//	_RequestSharkLibusb
//===========================================================================================================================

static void	_RequestSharkLibusb(int operate)
{
	if(gAppStubDelegate.requestLibusbOperate_f != NULL )
	{
		 gAppStubDelegate.requestLibusbOperate_f(0,operate);
	}
	_StopAirPlayServer();

}

//===========================================================================================================================
//	_RequestSharkLocationInfo
//===========================================================================================================================

static void	_RequestSharkLocationInfo(char * data)
{
	if(gAppStubDelegate.airPlayLocationInfoChanged_f != NULL )
	{
		 gAppStubDelegate.airPlayLocationInfoChanged_f(0, data);
	}

}

//===========================================================================================================================
//	_OnAccessoryVidPidGet
//===========================================================================================================================

static void	_OnAccessoryVidPidGet(void * vid,void * pid)
{

	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryVidPidGet. \n");
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,vid,pid,&len);

}

//===========================================================================================================================
//	_OnCallKeyEvent
//===========================================================================================================================

static void	_OnCallKeyEvent(int keyCode,int keyAction)
{

	app_ulog( kLogLevelNotice, "AppStub __OnCallKeyEvent keyCode = %d \n",keyCode);
	gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, keyCode, 0 );
}

//===========================================================================================================================
//	_OnAccessoryStartHID
//===========================================================================================================================

static void	_OnAccessoryStartHID(unsigned char * hidDescriptorPtr,int hidDescriptorLen)
{

	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryStartHID len = %d \n",hidDescriptorLen);
	if( gHidDriverFd < 0)
	{
		gHidDriverFd = open("/dev/sd_reverse_hid", O_RDONLY, 0777);
	}
	struct hid_user_register_data32 rdata;
	rdata.report_desc = (__u32)hidDescriptorPtr;
	rdata.report_desc_len = hidDescriptorLen;
	rdata.user_id = mediaButtonIAP2UserId;
	ioctl(gHidDriverFd, HID_REGISTER, &rdata);
}
//===========================================================================================================================
//	_OnAccessoryHIDReport
//===========================================================================================================================

static void	_OnAccessoryHIDReport(unsigned char * data,int len)
{
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryHIDReport len = %d \n",len);
	struct hid_user_data32 hid_user;
	hid_user.data = (__u32)data;
	hid_user.data_len =  len;
	hid_user.user_id =  mediaButtonIAP2UserId;
	ioctl(gHidDriverFd, HID_SET_DATA, &hid_user);

}

//===========================================================================================================================
//	_OnAccessoryWifiConfigurationInformation
//===========================================================================================================================

static void	_OnAccessoryWifiConfigurationInformation(const void * data)
{
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryWifiConfigurationInformation.\n");
	if(gAppStubDelegate.airPlayWifiConfigurationInformation_f != NULL )
	{
		 gAppStubDelegate.airPlayWifiConfigurationInformation_f(0, data);
	}

}

static void *bonjourIPAddress = NULL;
static int bonjouPort = 0;
static void * bonjourSourceVersion = NULL;
static void * bonjourDeviceID = NULL;
static void * bonjourPublicKey = NULL;

static void _CarPlayControlChangeModeTimer(  void *eventInfo)
{
	if( mediaButtonSource != NULL )
	{
		dispatch_source_forget( &mediaButtonSource );
	}

	if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL ){
		gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 126, 0 );
	}
	app_ulog_err( kLogLevelNotice, "_CarPlayControlChangeModeTimer.\n" );

}

static void _CarPlayControlClientHttpInfoTimer(  int signalNumber/*void *eventInfo*/)
{
//	if( source != NULL )
//	{
//		dispatch_source_forget( &source );
//	}
	OSStatus 					err;
	if( strlen( gDeviceId ) == 0 )
	{
		memcpy(gDeviceId, bonjourDeviceID, 18); //修改直接复制deviceID,让其直接连接
		app_ulog_err( kLogLevelNotice, "_CarPlayControlClientHttpInfoTimer No iAP2 deviceID, iAP2 Authentication may not be complete yet.\n" );
	}

	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
	app_ulog( kLogLevelWarning, "CarPlayControlClientConnect vid:0x%04x pid:0x%04x \n", vid, pid);

	while( gCarPlayControlClient == NULL  )
	{
		usleep(200*1000);
		app_ulog( kLogLevelWarning, "CarPlayControlClientConnect gCarPlayControlClient null wait \n");
	}
	for( int i = 0; i < 1; i++ ) {
		err = CarPlayControlClientConnectWithCarPlaySession( gCarPlayControlClient,
				bonjourIPAddress, bonjouPort,bonjourSourceVersion,bonjourDeviceID,bonjourPublicKey,vid,pid );
		app_ulog( kLogLevelNotice, "CarPlayControlClientConnect %s: %#m\n", err ? "failed" : "succeeded", err );

		if( err != kNoErr ) {
			sleep( 1 );
			continue;
		} else {
			break;
		}
	}

}


//===========================================================================================================================
//	_OnAccessoryHttpInfoReport
//===========================================================================================================================

static void	_OnAccessoryHttpInfoReport(void *ipAddress, int port,void * srcVersion,void * deviceID,void * publicKey)
{
	if( source != NULL )
	{
		dispatch_source_forget( &source );
	}

	OSStatus 					err;
	char * app_ipAddress = (char *)ipAddress;
	char * app_srcVersion = (char *)srcVersion;
	char * app_deviceID = (char *)deviceID;
	char * app_publicKey = (char *)publicKey;
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryHttpInfoReport app_ipAddress = %s \n",app_ipAddress);
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryHttpInfoReport app_port = %d \n",port);
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryHttpInfoReport app_srcVersion = %s \n",app_srcVersion);
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryHttpInfoReport app_deviceID = %s \n",app_deviceID);
	app_ulog( kLogLevelNotice, "AppStub _OnAccessoryHttpInfoReport app_publicKey = %s \n",app_publicKey);

	bonjourIPAddress = ipAddress;
	bonjouPort = port;
	bonjourSourceVersion = srcVersion;
	bonjourDeviceID = deviceID;
	bonjourPublicKey = publicKey;

	signal(SIGALRM, _CarPlayControlClientHttpInfoTimer);
	struct itimerval timer;
	//The accessory vid:0x8087 pid:0x0a68
	timer.it_value.tv_sec = 2;

	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
	//0x3518 pid:0x0003
	if( (vid == 0x8087 && pid == 0x0a68) || (vid == 0x3518 && pid == 0x0003) )
	{
		timer.it_value.tv_sec = 0; //0S
	}
	timer.it_value.tv_usec = 0;
	timer.it_interval.tv_sec = 0;
	timer.it_interval.tv_usec = 0;
	setitimer(ITIMER_REAL, &timer, NULL);

//	uint64_t nanos=(int64_t)(3 * NSEC_PER_SEC); //2S
//	source = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, eventQueue);
//	dispatch_set_context( source, NULL );
//	dispatch_source_set_event_handler_f( source, _CarPlayControlClientHttpInfoTimer );
//	dispatch_source_set_timer( source, dispatch_time( DISPATCH_TIME_NOW, nanos ), nanos, nanos );
//	dispatch_resume( source );

}

//===========================================================================================================================
//	_SetupUI
//===========================================================================================================================

static OSStatus	_SetupUI( void )
{
	OSStatus			err = kNoErr;

	// $$$ TODO: Set up the UI framework application and window for drawing and receiving user events.
	//InitStreamPlayer();

	require_noerr( err, exit );

exit:
	if( err ) _TearDownUI();
	return( err );
}

//===========================================================================================================================
//	_TearDownUI
//===========================================================================================================================

static void	_TearDownUI( void )
{
	// $$$ TODO: Destroy up the UI framework application and window for drawing and receiving user events.
	app_ulog_err( kLogLevelNotice, "AirPlay is _TearDownUI successfully.\n" );
}

//===========================================================================================================================
//	_RunUI
//===========================================================================================================================

static void	_RunUI( void )
{
	// $$$ TODO: Get user input and send HID reports to AirPlay.
	// For an example of creating a virtual HID knob report see KnobUpdate (void).

	iAP2InterfaceInitial();

	CarPlay2iAP2InterfaceDelegate	c2iDelegate;

	CarPlay2iAP2InterfaceDelegateInit( &c2iDelegate );
	c2iDelegate.carPlayStartBonjour_f	= _CarPlayControllerConnect;
	c2iDelegate.sendiAPMessage_f 		= _AirPlayReceiverSessionSendiAPMessage;
	c2iDelegate.startAirPlayServer_f	= _StartAirPlayServer;
	c2iDelegate.end_f					= _AirPlayReceiverSessionEnd;
	c2iDelegate.stopAirPlayServer_f		= _StopAirPlayServer;
	c2iDelegate.controllerEqual_f		= _CarPlayControllerEqual;
	c2iDelegate.accessoryRead_f			= _CarPlayAccessoryRead;
	c2iDelegate.accessoryWrite_f	    = _CarPlayAccessoryWrite;
	c2iDelegate.publishSharkBonjour_f	    = _PublishSharkBonjour;
	c2iDelegate.accessoryIdentification_f	= _IdentificationInfo;
	c2iDelegate.requestSharkDeviceId_f	    = _RequestSharkDeviceId;
	c2iDelegate.requestSharkLibusb_f	    = _RequestSharkLibusb;
	c2iDelegate.requestSharkLocationInfo_f	= _RequestSharkLocationInfo;
	c2iDelegate.readUsbVidPid_f	            = _OnAccessoryVidPidGet;
	c2iDelegate.onCallEvent_f	            = _OnCallKeyEvent;
	c2iDelegate.accessoryStartHID_f	        = _OnAccessoryStartHID;
	c2iDelegate.accessoryHIDReport_f	    = _OnAccessoryHIDReport;
	c2iDelegate.accessoryWifiInfo_f	        = _OnAccessoryWifiConfigurationInformation;
	c2iDelegate.accessoryHttpInfoReport_f	= _OnAccessoryHttpInfoReport;
	CarPlay2iAP2InterfaceSetDelegate( &c2iDelegate );
	iAP2OverCarPlaySetup();
	app_ulog( kLogLevelNotice, "AirPlay starting _RunUI\n");

}

//===========================================================================================================================
//	_CarPlayControllerEqual
//===========================================================================================================================

static int	_CarPlayControllerEqual()
{
	CarPlayControllerRef		controller;
	CFIndex						count;
	uint8_t						deviceID[ 6 ];
	uint8_t						deviceIDCString[ 18 ];

    app_ulog( kLogLevelNotice, "_CarPlayControllerEqual comming.\n" );

	count = CFArrayGetCount( gCarPlayControllers );

	if( count == 0 )
	{
		app_ulog( kLogLevelNotice, "CFMutableArrayRef CarPlayControllers is empty.\n" );
		return 0;
	}

	controller = (CarPlayControllerRef) CFArrayGetValueAtIndex( gCarPlayControllers, 0 );

	CarPlayControllerGetBluetoothMacAddress( controller, deviceID );
	MACAddressToCString( deviceID, deviceIDCString );

//	if( stricmp( gDeviceId, deviceIDCString ) != 0 )
//	{
//		app_ulog_err( kLogLevelNotice, "CarPlayControllerEqual strerr iAP2 deviceId: %s not equal Add/Update deviceID: %s.\n", gDeviceId, deviceIDCString );
//		return 0;
//	} //Tinker shark

	return 1;
}

//===========================================================================================================================
//	_CarPlayControllerConnect
//===========================================================================================================================
/**
 * 请求向accessory发送数据
 *
 */
static int _CarPlayAccessoryWrite(int handle,signed char * data, signed short len,int *length){
	return gAppStubDelegate.requestAccessoryWrite_f(handle,data,len,length);
}

/**
 * 请求向accessory读取数据
 *
 */
static int _CarPlayAccessoryRead(int handle,signed char * data, signed short len,int *length){
	return gAppStubDelegate.requestAccessoryRead_f(handle,data,len,length);
}

static void _CarPlayControlClientHttpEvent(int event )
{
	app_ulog( kLogLevelNotice, "_CarPlayControlClientHttpEvent event: %d.\n",event );

}


static void	_CarPlayControllerConnect()
{
	CarPlayControllerRef		controller;
	OSStatus 					err;
	CFIndex						count;
	uint8_t						deviceID[ 6 ];
	uint8_t						deviceIDCString[ 18 ];

    app_ulog( kLogLevelNotice, "_CarPlayControllerConnect comming. \n" );


	count = CFArrayGetCount( gCarPlayControllers );

	if( count == 0 )
	{
		app_ulog_err( kLogLevelNotice, "CarPlayControllerConnect strerr local CarPlayControllers count is 0.\n" );
		return;
	}

	controller = (CarPlayControllerRef) CFArrayGetValueAtIndex( gCarPlayControllers, 0 );

	CarPlayControllerGetBluetoothMacAddress( controller, deviceID );
	MACAddressToCString( deviceID, deviceIDCString );

	if( strlen( gDeviceId ) == 0 )
	{
		app_ulog_err( kLogLevelNotice, "CarPlayControllerConnect strerr iAP2 deviceId: %s not equal Add/Update deviceID: %s.\n", gDeviceId, deviceIDCString );
		return;
	}

	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
	app_ulog( kLogLevelWarning, "CarPlayControlClientConnect vid:0x%04x pid:0x%04x \n", vid, pid);

	for( int i = 0; i < 5; i++ ) {
		err = CarPlayControlClientConnect( gCarPlayControlClient, controller,vid,pid );
		app_ulog( kLogLevelNotice, "CarPlayControlClientConnect %s: %#m\n", err ? "failed" : "succeeded", err );

		if( err != kNoErr ) {
			sleep( 1 );
			continue;
		} else {
			break;
		}
	}
}

static void _CarPlayControlClientEventTimer(  void *eventInfo)
{
	app_ulog_err( kLogLevelNotice, "_CarPlayControlClientEventTimer.\n" );

	if( source != NULL )
	{
		dispatch_source_forget( &source );
	}
	CarPlayControllerRef controller = (CarPlayControllerRef)eventInfo;

	CFIndex 	count, serviceCount;
	uint8_t		deviceID[ 6 ];
	uint8_t		deviceIDCString[ 18 ];

	// analyse the CarPlayController

	// deviceID

	CarPlayControllerGetBluetoothMacAddress( controller, deviceID );
	MACAddressToCString( deviceID, deviceIDCString );

	// servicesCount

	CarPlayControllerGetServicesCount( controller, &serviceCount );

	count = CFArrayGetCount( gCarPlayControllers );

	app_ulog( kLogLevelNotice, "CarPlayControlClientEvent Add/Update CarPlayController deviceID: %s, CarPlayControllers Count: %d.\n", deviceIDCString, count );

	// No iAP2 deviceID, iAP2 Authentication may not be complete yet.
	app_ulog_err( kLogLevelNotice, "Bonjour found peer deviceID: %s .\n",deviceIDCString );

	if( strlen( gDeviceId ) == 0 )
	{
		memcpy(gDeviceId, deviceIDCString, 18); //修改直接复制deviceID,让其直接连接
		app_ulog_err( kLogLevelNotice, "No iAP2 deviceID, iAP2 Authentication may not be complete yet.\n" );

		if( count > 0 )	CFArrayRemoveAllValues( gCarPlayControllers );
		CFArrayAppendValue( gCarPlayControllers, controller );
	}

	// With iAP2 deviceID, iAP2 Authentication already is complete. If iAP2 deviceID not equal deviceID, ignore.

    if (stricmp(gDeviceId, deviceIDCString) != 0) {
        app_ulog_err(kLogLevelNotice, "iAP2 deviceId: %s not equal Add/Update deviceID: %s, ignore the CarPlayController.\n",
                     gDeviceId, deviceIDCString);
        return;
    }

	// iAP2 deviceID equal deviceID, then update bonjour services.


    Boolean updateBonjour = true;
    app_ulog_err( kLogLevelNotice, "iAP2 Bojour controller count = %d \n",count);
	if( count > 0 )
	{
		for(int i=0;i< count; i++)
		{
			CarPlayControllerRef		oldController;
			oldController = (CarPlayControllerRef) CFArrayGetValueAtIndex( gCarPlayControllers, i );

			uint8_t		oldDeviceID[ 6 ];
			uint8_t		oldDeviceIDCString[ 18 ];

			// analyse the CarPlayController

			// deviceID

			CarPlayControllerGetBluetoothMacAddress( oldController, oldDeviceID );
			MACAddressToCString( oldDeviceID, oldDeviceIDCString );
			app_ulog_err( kLogLevelNotice, "iAP2 Bojour controller oldDeviceIDCString = %s \n",oldDeviceIDCString);

//			 if (stricmp(oldDeviceIDCString, deviceIDCString) == 0) {
//				 //已经存在旧的设备ID
//				 updateBonjour = false;
//				 app_ulog_err( kLogLevelNotice, "iAP2 oldDeviceIDCString equal deviceIDCString, then cancel update bonjour services.\n" );
//			 }
		}
		CFArrayRemoveAllValues( gCarPlayControllers );
	}
	CFArrayAppendValue( gCarPlayControllers, controller );

	if( updateBonjour )
	{
		app_ulog_err( kLogLevelNotice, "iAP2 deviceID equal deviceID, then update bonjour services.\n" );
		BonjourCarPlayAvailability();
	}
}

//===========================================================================================================================
//	_CarPlayControlClientEventCallback
//===========================================================================================================================
static void _CarPlayControlClientEventCallback( CarPlayControlClientRef client, CarPlayControlClientEvent event, void *eventInfo, void *context )
{
    (void) client;
	(void) context;
	CarPlayControllerRef controller = (CarPlayControllerRef)eventInfo;
	OSStatus err;

    app_ulog( kLogLevelNotice, "_CarPlayControlClientEventCallback event = %d. \n", event );

	if (event == kCarPlayControlClientEvent_AddOrUpdateController)
	{
		CFIndex 	count, serviceCount;
		uint8_t		deviceID[ 6 ];
		uint8_t		deviceIDCString[ 18 ];

		// analyse the CarPlayController

		// deviceID

		CarPlayControllerGetBluetoothMacAddress( controller, deviceID );
		MACAddressToCString( deviceID, deviceIDCString );

		// servicesCount

		CarPlayControllerGetServicesCount( controller, &serviceCount );

//		if( serviceCount == 2 )
//		{
//			app_ulog( kLogLevelNotice, "ignore serviceCount 2 of bonjour.\n" );
//			return;
//		}

		count = CFArrayGetCount( gCarPlayControllers );

		app_ulog( kLogLevelNotice, "CarPlayControlClientEvent Add/Update CarPlayController deviceID: %s, CarPlayControllers Count: %d.\n", deviceIDCString, count );

		// No iAP2 deviceID, iAP2 Authentication may not be complete yet.
		app_ulog_err( kLogLevelNotice, "Bonjour found peer deviceID: %s .\n",deviceIDCString );
		app_ulog_err( kLogLevelNotice, "Bonjour found peer gDeviceId: %s .\n",gDeviceId );
		if( strlen( gDeviceId ) == 0 )
		{
			uint64_t nanos=(int64_t)(2 * NSEC_PER_SEC); //2S

			uint16_t vid = 0x0000;
			uint16_t pid = 0x0000;
			int len = 2;
			gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
			// 现代主机 vid:0x05ac pid:0x0000 ，一定要立马连接
			// 0x0508 pid:0x0202
			Boolean isGarmin = (vid == 0x091e && pid == 0x0003);
			if(( vid == 0x05ac && pid == 0x0000)
					|| ( vid == 0x0508 && pid == 0x0202) || isGarmin)
			{
				 nanos = (int64_t)(0 * NSEC_PER_SEC); //0S
			}
			//Infiniti英菲尼迪 vid:0x0424 pid:0x4920
			if(( vid == 0x0424 && pid == 0x4920))
			{
				 nanos = (int64_t)(3 * NSEC_PER_SEC); //3S
				 app_ulog_err( kLogLevelNotice, "Bonjour found peer Infinite wait 3 seconds.\n" );
			}

			// 0x04da pid:0x3210 skoda主机一定要永久等待  RAM主机: vid:0x1a96 pid:0x2025
			// Kicks主机vid:0x9487 pid:0x0001
			// 标雪主机 vid:0x108c pid:0x0178
			// 大众主机 vid:0x2f18 pid:0x0101  Infotainment VW
			if(( vid == 0x1a96 && pid == 0x2025)
					 || ( vid == 0x9487 && pid == 0x0001)
					 || ( vid == 0x108c && pid == 0x0178)
					 || ( vid == 0x2f18 && pid == 0x0101))
			{
				 nanos=(int64_t)(2 * NSEC_PER_SEC); //2S
			}
			// vid:0x1d6b pid:0x0104 老马自达
			char	cpStartSession [ 10 ] = { 0 };
			// __system_property_get("sys.shark.cp.start.session", cpStartSession);
			char	carName [ 100 ];
			// __system_property_get("sys.shark.oem.car.name", carName);
			Boolean isMazda = (vid == 0x1d6b && pid == 0x0104) && ( strcmp("Mazda",carName ) == 0 );
			// The Toyota vid:0x2207 pid:0x4e42
			Boolean isToyotaPRADO = (vid == 0x2207 && pid == 0x4e42) && ( strcmp("VLINE2",carName ) == 0 );
			// The 台湾现代主机 VENUE  0x05ac pid:0x0000
			Boolean isHyundaiVENUE = (vid == 0x05ac && pid == 0x0000) && ( strcmp("VENUE",carName ) == 0 );
			//The MG iSmart vid:0x2af8 pid:0x0104
			Boolean isMG = (vid == 0x2af8 && pid == 0x0104);//MG iSmart
			// 0x04da pid:0x3210
			Boolean isSkoda = (vid == 0x04da && pid == 0x3210);
//			vid:0x091e pid:0x0003
			app_ulog_err( kLogLevelNotice, "No iAP2 deviceID, iAP2 Authentication isMazda %d.\n",isMazda );
			if( (!isMazda) && (!isMG) && (!isToyotaPRADO) && (!isSkoda) && (!isHyundaiVENUE)/*  && ( strcmp("1",cpStartSession) == 0 )*/)
			{
				app_ulog_err( kLogLevelNotice, "No iAP2 deviceID, iAP2 Authentication may not be complete yet,wait %d nanos.\n" ,nanos);
				//老马自达一定要等车机端过来连接
//				 nanos=(int64_t)(3 * NSEC_PER_SEC); //3S

				source = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, eventQueue);
				dispatch_set_context( source, eventInfo );
				dispatch_source_set_event_handler_f( source, _CarPlayControlClientEventTimer );
				dispatch_source_set_timer( source, dispatch_time( DISPATCH_TIME_NOW, nanos ), nanos, nanos );
				dispatch_resume( source );
			}
			else {
				app_ulog_err( kLogLevelNotice, "No iAP2 deviceID, iAP2 Authentication may not be complete yet,wait forever seconds.\n" );
			}


			if( count > 0 )	CFArrayRemoveAllValues( gCarPlayControllers );
			CFArrayAppendValue( gCarPlayControllers, controller );

			goto exit;
		}

		// With iAP2 deviceID, iAP2 Authentication already is complete. If iAP2 deviceID not equal deviceID, ignore.

        if (stricmp(gDeviceId, deviceIDCString) != 0) {
            app_ulog_err(kLogLevelNotice, "iAP2 deviceId: %s not equal Add/Update deviceID: %s, ignore the CarPlayController.\n",
                         gDeviceId, deviceIDCString);
            goto exit;
        }

		// iAP2 deviceID equal deviceID, then update bonjour services.


        Boolean updateBonjour = true;
        app_ulog_err( kLogLevelNotice, "iAP2 Bojour controller count = %d \n",count);
		if( count > 0 )
		{
			for(int i=0;i< count; i++)
			{
				CarPlayControllerRef		oldController;
				oldController = (CarPlayControllerRef) CFArrayGetValueAtIndex( gCarPlayControllers, i );

				uint8_t		oldDeviceID[ 6 ];
				uint8_t		oldDeviceIDCString[ 18 ];

				// analyse the CarPlayController

				// deviceID

				CarPlayControllerGetBluetoothMacAddress( oldController, oldDeviceID );
				MACAddressToCString( oldDeviceID, oldDeviceIDCString );
				app_ulog_err( kLogLevelNotice, "iAP2 Bojour controller oldDeviceIDCString = %s \n",oldDeviceIDCString);

				 if (stricmp(oldDeviceIDCString, deviceIDCString) == 0) {
					 //已经存在旧的设备ID
					 updateBonjour = false;
					 app_ulog_err( kLogLevelNotice, "iAP2 oldDeviceIDCString equal deviceIDCString, then cancel update bonjour services.\n" );
				 }
			}
			CFArrayRemoveAllValues( gCarPlayControllers );
		}
		CFArrayAppendValue( gCarPlayControllers, controller );

		if( updateBonjour )
		{
			app_ulog_err( kLogLevelNotice, "iAP2 deviceID equal deviceID, then update bonjour services.\n" );
			BonjourCarPlayAvailability();
		}


	}
	else if (event == kCarPlayControlClientEvent_RemoveController)
	{
		CFIndex 	ndx, count;
		uint8_t		deviceID[ 6 ];
		uint8_t		deviceIDCString[ 18 ];

		count = CFArrayGetCount( gCarPlayControllers );
		ndx = CFArrayGetFirstIndexOfValue( gCarPlayControllers, CFRangeMake(0, count), controller );

		app_ulog_err( kLogLevelNotice, "CarPlayControlClientEvent Remove event received count: %d, ndx: %d\n", count, ndx );

		if( ndx == 0 )	CFArrayRemoveAllValues( gCarPlayControllers );

		if( source != NULL )
		{
			dispatch_source_forget( &source );
		}
	}
	else
	{
		app_ulog_err( kLogLevelNotice, "CarPlayControlClientEvent event type %d received\n", (int)event );
	}

exit:
	return;
}

static int net_detect()
{
	int fd = socket(AF_INET, SOCK_DGRAM, 0);
	struct ifreq ifr;
	strcpy(ifr.ifr_name, "usb0");
	if(ioctl(fd, SIOCGIFFLAGS, &ifr) < 0 ) {
		app_ulog( kLogLevelNotice, "_AirPlayThread: net_detect NOREADY.\n" );
		if(fd >= 0 )close( fd );
		return -1;
	}
	if(ifr.ifr_flags & IFF_RUNNING) {
		app_ulog( kLogLevelNotice, "_AirPlayThread: net_detect UP.\n" );
		if(fd >= 0 )close( fd );
		return 1;
	} else {
		app_ulog( kLogLevelNotice, "_AirPlayThread: net_detect READY.\n" );
		if(fd >= 0 )close( fd );
		return 0;
	}
}

//===========================================================================================================================
//	_AirPlayThread
//===========================================================================================================================

static void *	_AirPlayThread( void *inArg )
{
	OSStatus							err;
	AirPlayReceiverServerDelegate		delegate;

	(void) inArg;

	// Create the AirPlay server. This advertise via Bonjour and starts listening for connections.
	err = AirPlayReceiverServerCreate( &gAirPlayServer );
	require_noerr( err, exit );
    app_ulog( kLogLevelNotice, "_AirPlayThread: AirPlayReceiverServerCreate done.\n" );

	// Register ourself as a delegate to receive server-level events, such as when a session is created.

	AirPlayReceiverServerDelegateInit( &delegate );
	delegate.copyProperty_f		= _AirPlayHandleServerCopyProperty;
	delegate.sessionCreated_f = _AirPlayHandleSessionCreated;
	delegate.shark_ctrl_connect_f = _AirPlayHandleCtrlConnectRequest;
	AirPlayReceiverServerSetDelegate( gAirPlayServer, &delegate );

	// CarPlay Control

	err = CarPlayControlClientCreateWithServer( &gCarPlayControlClient, gAirPlayServer, _CarPlayControlClientEventCallback, NULL );
	require_noerr( err, exit );
    app_ulog( kLogLevelNotice, "_AirPlayThread: CarPlayControlClientCreateWithServer done.\n" );

	gCarPlayControllers = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
	require_action( gCarPlayControllers, exit, err = kNoMemoryErr );
	app_ulog( kLogLevelNotice, "_AirPlayThread: gCarPlayControllers CFArrayCreateMutable done.\n" );

	// Start the server and run until the app quits.
	CarPlayControlClientDelegate	c2clientDelegate;

	CarPlayControlClientDelegateInit( &c2clientDelegate );
	c2clientDelegate.shark_httpEvent_f	= _CarPlayControlClientHttpEvent;
	CarPlayControlClientSetDelegate( &c2clientDelegate );

	char ncm [10] = { 0 };
	memset( ncm ,0 ,10);
	while( net_detect() < 1)
	{
		usleep(100*1000);
		memset( ncm ,0 ,10);
		// __system_property_get("sys.lynx.usb.ncm", ncm);
		if(strcmp( ncm, "0" ) == 0)
		{
			app_ulog( kLogLevelWarning, "_AirPlayThread: ncm 0");
			exit(0);
			break;
		}
	}
    AirPlayReceiverServerStart( gAirPlayServer ); //Tinker start HttpServer
    CarPlayControlClientStart( gCarPlayControlClient ); //Tinker start browse the BonjourService

	CFRunLoopRun();
	CarPlayControlClientStop( gCarPlayControlClient );
	AirPlayReceiverServerStop( gAirPlayServer );

exit:
	CFReleaseNullSafe( gCarPlayControllers );
	CFReleaseNullSafe( gCarPlayControlClient );
	CFReleaseNullSafe( gAirPlayServer );
	return( NULL );
}

//===========================================================================================================================
//	_AirPlayHandleServerCopyProperty
//===========================================================================================================================

static CFTypeRef
	_AirPlayHandleServerCopyProperty(
		AirPlayReceiverServerRef	inServer,
		CFStringRef					inProperty,
		CFTypeRef					inQualifier,
		OSStatus *					outErr,
		void *						inContext )
{
	CFTypeRef		value = NULL;
	OSStatus		err;

	(void) inServer;
	(void) inQualifier;
	(void) inContext;

	if( 0 ) {}

	// AudioFormats

	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_AudioFormats ) ) )
	{
		value = _getAudioFormats( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}

	// AudioLatencies

	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_AudioLatencies ) ) )
	{
		value = _getAudioLatencies( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}

	// BluetoothIDs

	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_BluetoothIDs ) ) )
	{
		if( gBluetoothIDs == NULL )
		{
			char	bluetoothAddress[ 20 ] = { 0 };
			_getBluetoothAddress( bluetoothAddress );

			err = CFArrayEnsureCreatedAndAppendCString( &gBluetoothIDs, bluetoothAddress, strlen( bluetoothAddress ) );
			require_noerr( err, exit );
		}

		value = gBluetoothIDs;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_RightHandDrive ) ) )
    {
        value = gRightHandDrive;
        require_action_quiet( value, exit, err = kNotHandledErr );
        CFRetain( value );
    }
    else if( CFEqual( inProperty, CFSTR( kAirPlayKey_NightMode ) ) )
    {
        value = gNightMode;
        require_action_quiet( value, exit, err = kNotHandledErr );
        CFRetain( value );
    }
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_LimitedUI ) ) )
	{
		if( !gLimitedUI )	gLimitedUI = kCFLBooleanFalse;
		value = gLimitedUI;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_LimitedUIElements ) ) )
	{
		CFMutableArrayRef features;

		features = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( features, exit, err = kNoMemoryErr );

		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_SoftKeyboard ) );
		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_SoftPhoneKeypad ) );
		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_NonMusicLists ) );
		CFArrayAppendValue( features, CFSTR( kAirPlayLimitedUIElement_MusicLists ) );

		value = features;
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_VehicleInformation ) ) )
	{
		CFMutableDictionaryRef vehicleDict;

		vehicleDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
		require_action_quiet( vehicleDict, exit, err = kNotHandledErr );
		if( gETCSupported ) {
			// Support ETC
			CFMutableDictionaryRef dict = NULL;
			dict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( dict )
			{
				// Start with ETC active
				CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_Active), true );

				CFDictionarySetValue( vehicleDict, CFSTR( kAirPlayVehicleInformation_ETC), dict );
				ForgetCF( &dict );
				dict = NULL;
			}
		}
		if( CFDictionaryGetCount( vehicleDict ) == 0 ) {
			ForgetCF( &vehicleDict );
			err = kNotHandledErr;
			goto exit;
		} else {
			value = vehicleDict;
        	CFRetain( value );
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_ExtendedFeatures ) ) )
	{
		CFMutableArrayRef extendedFeatures;

		extendedFeatures = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( extendedFeatures, exit, err = kNotHandledErr );

		CFArrayAppendValue( extendedFeatures, CFSTR( kAirPlayExtendedFeature_VocoderInfo ) );

		if( gEnhancedRequestCarUI ) {
			CFArrayAppendValue( extendedFeatures, CFSTR( kAirPlayExtendedFeature_EnhancedRequestCarUI ) );
		}

		if( CFArrayGetCount( extendedFeatures ) == 0 ) {
			ForgetCF( &extendedFeatures );
		} else {
			value = extendedFeatures;
        	CFRetain( value );
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_OEMIcons ) ) )
	{
		CFTypeRef obj;
		CFMutableDictionaryRef iconDict;
		CFMutableArrayRef iconsArray;

		iconsArray = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( iconsArray, exit, err = kNotHandledErr );

		// Add icons for each required size
		obj = CFDataCreateWithFilePath( "/app/carplay/res/oemIcons/icon_120x120.png", NULL );
		if( obj ) {
			iconDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( iconDict ) {
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_WidthPixels ), 120 );
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_HeightPixels ), 120 );
				CFDictionarySetBoolean( iconDict, CFSTR( kAirPlayOEMIconKey_Prerendered ), true );
				CFDictionarySetValue( iconDict, CFSTR( kAirPlayOEMIconKey_ImageData ), obj );
				CFArrayAppendValue( iconsArray, iconDict );
				CFRelease( iconDict );
				iconDict = NULL;
			}
			CFRelease( obj );
		}
		obj = CFDataCreateWithFilePath( "/app/carplay/res/oemIcons/icon_180x180.png", NULL );
		if( obj ) {
			iconDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( iconDict ) {
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_WidthPixels ), 180 );
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_HeightPixels ), 180 );
				CFDictionarySetBoolean( iconDict, CFSTR( kAirPlayOEMIconKey_Prerendered ), true );
				CFDictionarySetValue( iconDict, CFSTR( kAirPlayOEMIconKey_ImageData ), obj );
				CFArrayAppendValue( iconsArray, iconDict );
				CFRelease( iconDict );
				iconDict = NULL;
			}
			CFRelease( obj );
		}
		obj = CFDataCreateWithFilePath( "/app/carplay/res/oemIcons/icon_256x256.png", NULL );
		if( obj ) {
			iconDict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			if( iconDict ) {
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_WidthPixels ), 256 );
				CFDictionarySetInt64( iconDict, CFSTR( kAirPlayOEMIconKey_HeightPixels ), 256 );
				CFDictionarySetBoolean( iconDict, CFSTR( kAirPlayOEMIconKey_Prerendered ), true );
				CFDictionarySetValue( iconDict, CFSTR( kAirPlayOEMIconKey_ImageData ), obj );
				CFArrayAppendValue( iconsArray, iconDict );
				CFRelease( iconDict );
				iconDict = NULL;
			}
			CFRelease( obj );
		}
		if( CFArrayGetCount( iconsArray ) > 0) {
			value = iconsArray;
			CFRetain( value );
		} else {
			CFRelease( iconsArray );
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_Displays ) ) )
	{
		value = _getScreenDisplays( &err );
		// value = _getScreenDisplaysWindowConfig( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_HIDDevices ) ) )
	{
		value = _getHIDDevices( &err );
		require_noerr( err, exit );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_ClientOSBuildVersionMin ) ) )
	{
		if( giOSVersionMin )
		{
			value = giOSVersionMin; // ie. CFSTR( "11D257" )
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayProperty_DeviceID ) ) )
	{
		// [ 89911 ] The accessory's Wi-Fi MAC address can be found in ATS > CarPlay session > Info Response > deviceID

		uint8_t		outMACAddress[ 6 ];
		char		MACAddressCString[ 20 ] = { 0 };
		GetInterfaceMACAddress( "usb0", outMACAddress );
		MACAddressToCString( outMACAddress, MACAddressCString );

		gDeviceID = CFStringCreateWithCString( kCFAllocatorDefault, MACAddressCString, kCFStringEncodingUTF8 );

		value = gDeviceID;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_AccessoryEnabledFeatures ) ) )
	{
		CFMutableArrayRef features;

		features = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
		require_action_quiet( features, exit, err = kNoMemoryErr );

		// Enable if controller (and accessory supports each feature)
		if( gSupportsAltScreen )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_AltScreen ) );
		if( gSupportsUIContext )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_UIContext ) );
		if( gSupportsEnhancedSiri )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_EnhancedSiri ) );
		if( gSupportsViewAreas )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_ViewAreas ) );
		/*
		if( gSupportsCornerMasks )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_CornerMasks ) );
		*/

		/*
		if( gSupportsFocusTransfer )
			CFArrayAppendValue( features, CFSTR( kAirPlayKeyAccessoryEnabledFeature_FocusTransfer ) );
		*/

		value = features;
		CFRetain( value );
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_InfoAltScreenSuggestUIURLs ) ) )
	{
		if( gSupportsAltScreen )
		{
			// Include any URLs that you want the device to suggest using suggestUI
			CFMutableArrayRef urls;

			urls = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action_quiet( urls, exit, err = kNotHandledErr );

			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterMap ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterInstructionCard ) );

			value = urls;
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_InfoUIContextLastOnDisplayURLs ) ) )
	{
		if( gSupportsUIContext )
		{
			// Include any URLs that you want to support
			CFMutableArrayRef urls;

			urls = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action_quiet( urls, exit, err = kNotHandledErr );

			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextMaps ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextPhone ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextMessages ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextNowPlaying ) );

			value = urls;
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_InfoUIContextNowOnDisplayURLs ) ) )
	{
		if( gSupportsUIContext )
		{
			// Include any URLs that you want to support
			CFMutableArrayRef urls;

			urls = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			require_action_quiet( urls, exit, err = kNotHandledErr );

			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextNowPlaying ) );
			CFArrayAppendValue( urls, CFSTR( kAirPlayKey_ContextMaps ) );

			value = urls;
			CFRetain( value );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else if( CFEqual( inProperty, CFSTR( kAirPlayKey_EnhancedSiriInfo ) ) )
	{
		if( gSupportsEnhancedSiri )
		{
			CFMutableDictionaryRef dict;
			CFMutableArrayRef languages;

			dict = CFDictionaryCreateMutable( NULL, 0,  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			require_action_quiet( dict, exit, err = kNoMemoryErr );

			languages = CFArrayCreateMutable( NULL, 0, &kCFTypeArrayCallBacks );
			if( !languages )
			{
				CFRelease( dict );
				err = kNoMemoryErr;
				goto exit;
			}
			// CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_EnhancedSiriButton ), true );
			// CFDictionarySetBoolean( dict, CFSTR( kAirPlayKey_EnhancedSiriVoice ), false );
			CFDictionarySetInt64( dict, CFSTR( kAirPlayKey_SupportedSiriTriggerZones ), kAirPlaySiriTriggerZone_NotApplicable );
			CFDictionarySetValue( dict, CFSTR( kAirPlayKey_VoiceModelCurrentLanguage ), CFSTR( kLanguageRegion_Chinese_MandarinChina ) );

			CFArrayAppendValue( languages, CFSTR( kLanguageRegion_English_UnitedStates ) );
			CFArrayAppendValue( languages, CFSTR( kLanguageRegion_Chinese_MandarinChina ) );
			CFDictionarySetValue( dict, CFSTR( kAirPlayKey_VoiceModelSupportedLanguages ), languages );
			CFRelease( languages );
			value = dict;
			CFRetain( dict );
		}
		else
		{
			err = kNotHandledErr;
			goto exit;
		}
	}
	else
	{
		err = kNotHandledErr;
		goto exit;
	}
	err = kNoErr;

exit:
	if( outErr ) *outErr = err;
	return( value );
}

//===========================================================================================================================
//	_AirPlayReceiverSessionEnd
//===========================================================================================================================

static void _AirPlayReceiverSessionEnd()
{
	app_ulog( kLogLevelNotice, "ends the current AirPlay session.\n" );

	if( gAirPlaySession )	AirPlayReceiverSessionEnd( gAirPlaySession );
	app_ulog( kLogLevelNotice, "AirPlay session stoped.\n" );
	if(gAppStubDelegate.airPlaySessionStoped_f != NULL){
		gAppStubDelegate.airPlaySessionStoped_f(100);
	}
	gAirPlayModeStateRaw.screen = kAirPlayEntity_NotApplicable;
	gAirPlayModeStateRaw.permScreen   = kAirPlayEntity_NotApplicable;
	gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_NotApplicable;
	gAirPlayModeStateRaw.permMainAudio = kAirPlayEntity_NotApplicable;
}

//===========================================================================================================================
//	_AirPlayHandleSessionCreated
//===========================================================================================================================

static void
	_AirPlayHandleSessionCreated(
		AirPlayReceiverServerRef	inServer,
		AirPlayReceiverSessionRef	inSession,
		void *						inContext )
{
	AirPlayReceiverSessionDelegate		delegate;
	(void) inServer;
	(void) inContext;

	app_ulog_err( kLogLevelNotice, "AirPlay session created.\n" );
	gAirPlaySession = inSession;

	// Register ourself as a delegate to receive session-level events, such as modes changes.

	AirPlayReceiverSessionDelegateInit( &delegate );
	delegate.finalize_f		= _AirPlayHandleSessionFinalized;
	delegate.started_f		= _AirPlayHandleSessionStarted;
	delegate.copyProperty_f	= _AirPlayHandleSessionCopyProperty;
	delegate.modesChanged_f	= _AirPlayHandleModesChanged;
	delegate.initialModes_f	= _AirPlayHandleInitialModes;
	delegate.changeModes_f	= _AirPlayHandleChangeModes;
	delegate.getModesChanged_f	= _AirPlayHandleGetModesChanged;
	delegate.getAirPlayModeState_f	= _AirPlayHandleGetAirPlayModeState;
	delegate.getAirPlayDisplayInfo_f	= _AirPlayHandleGetDisplayInfo;
	delegate.requestUI_f	= _AirPlayHandleRequestUI;
	delegate.requestSiri_f	= _AirPlayHandleRequestSiri;
	delegate.control_f		= _AirPlayHandleSessionControl;
	delegate.setEnhancedSiriParams_f = _AirPlayHandleSetEnhancedSiriParams;
	delegate.suggestUI_f = _AirPlayHandleSuggestUI;
	delegate.duckAudio_f	= _AirPlayHandleDuckAudio;
	delegate.unduckAudio_f  = _AirPlayHandleunDuckAudio;
	delegate.screenStart_f  = _AirPlayHandleSessionScreenStart;
	delegate.screenStop_f   = _AirPlayHandleSessionScreenStop;
	delegate.forceKeyFrame_f   = _AirPlayHandleSessionForceKeyFrame;
	delegate.setNightMode_f   = _AirPlayHandleSessionSetNightMode;
	delegate.updateViewAreaIndex_f   = _AirPlayHandleSessionUpdateViewAreaIndex;
	delegate.display_f  = _AirPlayHandleSessionDisplay;
	delegate.audioFocusChanged_f  = _AirPlayHandleAudioFocusChanged;
	delegate.info_f  = _AirPlayHandleSessionInfo;
	delegate.hidReport_f  = _AirPlayHandleSessionHIDReport;
	delegate.audioLatencyReport_f  = _AirPlayHandleSessionAudioLatencyReport;
	delegate.audioFormatReport_f  = _AirPlayHandleSessionAudioFormatReport;
	delegate.audioStreamStart_f  = _AirPlayHandleSessionAudioStreamStart;
	delegate.audioStreamStop_f  = _AirPlayHandleSessionAudioStreamStop;
	delegate.getUsbVidPid_f  = _AirPlayHandleGetUsbVidPid;
	delegate.getAudioFormat_f  = _AirPlayHandleGetAudioFormat;
	AirPlayReceiverSessionSetDelegate( inSession, &delegate );

	AirPlaySessionCreated();
//	handleSessionCreated();

	// gSupportsViewAreas = AirPlayReceiverSessionHasFeatureViewAreas( inSession );
	// gSupportsUIContext = AirPlayReceiverSessionHasFeatureUIContext( inSession );
	// gSupportsEnhancedSiri =  AirPlayReceiverSessionHasFeatureEnhancedSiri( inSession );
	// gSupportsAltScreen = AirPlayReceiverSessionHasFeatureAltScreen( inSession );
	// gSupportsCornerMasks = AirPlayReceiverSessionHasFeatureCornerMasks( inSession );
	// gSupportsFocusTransfer = AirPlayReceiverSessionHasFeatureFocusTransfer( inSession );
}

//===========================================================================================================================
//	_AirPlayHandleCtrlConnectRequest
//===========================================================================================================================

static void
	_AirPlayHandleCtrlConnectRequest(
		AirPlayReceiverServerRef	inServer,
		AirPlayReceiverSessionRef	inSession,
		uint64_t					receiverDeviceID,
		void *						inContext )
{
	AirPlayReceiverSessionDelegate		delegate;
	(void) inServer;
	(void) inContext;

	app_ulog( kLogLevelNotice, "_AirPlayHandleCtrlConnectRequest.\n" );
	gAirPlaySession = inSession;


	char strReceiveID[200];
	snprintf( strReceiveID, sizeof(strReceiveID), "%llu", receiverDeviceID );

	app_ulog( kLogLevelNotice, "_AirPlayHandleCtrlConnectRequest peerDeviceId = %s \n" ,strReceiveID);

	uint8_t macDeviceID[ 6 ];
	memcpy(macDeviceID,0,6);

	WriteBig48(macDeviceID,receiverDeviceID);

	char	deviceIDCString[ 18 ];
	MACAddressToCString(macDeviceID, deviceIDCString);
	app_ulog( kLogLevelNotice, "_AirPlayHandleCtrlConnectRequest deviceIDCString %s \n" ,deviceIDCString);


	if(memcmp(gDeviceID, deviceIDCString, 18 )==0){
		app_ulog( kLogLevelNotice, "_AirPlayHandleCtrlConnectRequest gDeviceId %s already connected \n" ,deviceIDCString);
		return;
	}

	if( source != NULL )
	{
		dispatch_source_forget( &source );
	}

	memset( gDeviceId, 0, 18 );
	memcpy(gDeviceId,deviceIDCString,18);
	app_ulog( kLogLevelNotice, "_AirPlayHandleCtrlConnectRequest gDeviceId %s \n" ,deviceIDCString);

	//Look whether the airplay-ctrl Bonjour ready or not
	CFIndex						count;
	count = CFArrayGetCount( gCarPlayControllers ); //TODO 这里出现bug,对方来连接了，但是这里count = 0
	app_ulog( kLogLevelNotice, "_AirPlayHandleCtrlConnectRequest count %d .\n" ,count);
	if( count > 0 ){
		BonjourCarPlayAvailability();
	}

}


//===========================================================================================================================
//	_AirPlayHandleSessionFinalized
//===========================================================================================================================

static void	_AirPlayHandleSessionFinalized( AirPlayReceiverSessionRef inSession, void *inContext )
{
	(void) inSession;
	(void) inContext;

	// Do not delete gCarPlayControllers if support wireless CarPlay.

	gAirPlaySession = NULL;
	app_ulog_err( kLogLevelNotice, "AirPlay session ended.\n" );
	if(gAppStubDelegate.airPlaySessionStoped_f != NULL){
		gAppStubDelegate.airPlaySessionStoped_f(100);
	}
	AirPlaySessionFinalized();

}

//===========================================================================================================================
//	_GetMACAddressByIP
//===========================================================================================================================

static void _GetMACAddressByIP( const char * inCmd, const char * inIPAddr, char * outMACAddr )
{
    FILE *  file;
    char    buffer[ 128 ];
    char *  offset;

    file = popen( inCmd, "r" );
    if( !file )
    {
        app_ulog_err( kLogLevelNotice, "popen %s failed.\n", inCmd );
        goto exit;
    }

    while( fgets( buffer, 128, file ) )
    {
        offset = strstr( buffer, inIPAddr );
        if( offset )
        {
            offset = strstr( offset, "lladdr" );
            strcpy( outMACAddr, offset + 7 );
            outMACAddr[ 17 ] = '\0';
        }
    }

exit:
    if( file )
    {
        pclose( file );
        file = NULL;
    }
}

//===========================================================================================================================
//	_AirPlayHandleSessionStarted
//===========================================================================================================================

static void _AirPlayHandleSessionStarted( AirPlayReceiverSessionRef inSession, void *inContext )
{
	OSStatus error;
	CFNumberRef value;
	char	peerAddrCString[ 256 ] = { 0 };
	char	remoteMACAddress[ 18 ];

	(void) inContext;
	app_ulog( kLogLevelNotice, "AirPlay session started.\n" );
	telephoneButtonCycle = 0;
	if(gAppStubDelegate.airPlaySessionStarted_f!=NULL){
		gAppStubDelegate.airPlaySessionStarted_f(100);
	}
//
//	// The network interface name that is hosting the session
//	CFStringRef ifName = (CFStringRef)AirPlayReceiverSessionCopyProperty(inSession, 0, CFSTR(kAirPlayProperty_InterfaceName), NULL, &error );
//
//	// The remote IP address of the CarPlay session
//	CFStringRef peerAddr = (CFStringRef)AirPlayReceiverSessionCopyProperty(inSession, 0, CFSTR(kAirPlayProperty_PeerIPAddress), NULL, &error );
//
//	// Start a new iAP2 session over the CarPlay control channel only if the current CarPlay session is over wireless.
//	// Disconnecting iAP2 over Bluetooth should only occur when disableBluetooth is received in the AirPlayReceiverSessionControl_f
//	// delegate.
//	CFNumberRef transportTypeNumber = (CFNumberRef)AirPlayReceiverSessionCopyProperty( inSession, 0, CFSTR(kAirPlayProperty_TransportType), NULL, &error );
//
////    app_ulog( kLogLevelNotice, "The remote device of the CarPlay session IPAddress: %@, remoteMACAddress: %@.\n", peerAddr, remoteMACAddress );
//
//	if( error == kNoErr && transportTypeNumber ) {
//		uint32_t transportType;
//
//		CFNumberGetValue( (CFNumberRef) transportTypeNumber, kCFNumberSInt32Type, &transportType );
//		if( NetTransportTypeIsWiFi( transportType ) ) {
//
//			CFStringGetCString( peerAddr, peerAddrCString, 256, kCFStringEncodingUTF8 );
//			_GetMACAddressByIP( "ip neigh", peerAddrCString, remoteMACAddress );
//
////			handleSessionStarted( remoteMACAddress );
//
//
//
//			// This is a CarPlay session over wireless, start iAP2 over CarPlay.  For sending messages use
//			// AirPlayReceiverSessionSendiAPMessage() and for receiving message use the kAirPlayCommand_iAPSendMessage command
//			// in the AirPlayReceiverSessionControl_f delegate.
//			if( giAP2TransportType == kiAP2TransportType_HostUsb )
//			{
//				app_ulog_err( kLogLevelNotice, "Dont' Start a new iAP2 session over the CarPlay control channel because transportType: %d.\n", giAP2TransportType );
//				gWireConflictWireless = 1;
//				return;
//			}
//			app_ulog( kLogLevelNotice, "Start a new iAP2 session over the CarPlay control channel.\n" );
//			iAP2OverCarPlaySetup();
//		}
//	}
}

//===========================================================================================================================
//	_AirPlayHandleSessionCopyProperty
//===========================================================================================================================

static CFTypeRef
	_AirPlayHandleSessionCopyProperty(
		AirPlayReceiverSessionRef	inSession,
		CFStringRef					inProperty,
		CFTypeRef					inQualifier,
		OSStatus *					outErr,
		void *						inContext )
{
	CFTypeRef		value = NULL;
	OSStatus		err;

	(void) inSession;
	(void) inQualifier;
	(void) inContext;

	// Modes

	if( CFEqual( inProperty, CFSTR( kAirPlayProperty_Modes ) ) )
	{
		//value = createInitialModesForBackupCamera();
		//value = createInitialModesForController();
		value = gInitialModes;
		require_action_quiet( value, exit, err = kNotHandledErr );
		CFRetain( value );
	}
	else
	{
		err = kNotHandledErr;
		goto exit;
	}
	err = kNoErr;

exit:
	if( outErr ) *outErr = err;
	return( value );
}

//===========================================================================================================================
//	_AirPlayHandleModesChanged
//===========================================================================================================================

static void
	_AirPlayHandleModesChanged(
		AirPlayReceiverSessionRef 	inSession,
		const AirPlayModeState *	inState,
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

	static AirPlayModeState oldState;

	if( oldState.screen 		== inState->screen &&
		oldState.permScreen 	== inState->permScreen &&
		oldState.mainAudio		== inState->mainAudio &&
		oldState.permMainAudio 	== inState->permMainAudio &&
		oldState.speech.entity 	== inState->speech.entity &&
		oldState.speech.mode 	== inState->speech.mode &&
		oldState.phoneCall 		== inState->phoneCall &&
		oldState.turnByTurn 	== inState->turnByTurn )
	{
		return;
	}

	memcpy( &oldState, inState, sizeof( AirPlayModeState ) );
	memcpy( &gAirPlayModeStateRaw, inState, sizeof( AirPlayModeState ) );

	app_ulog( kLogLevelNotice, "Modes changed: screen %s (perm %s), mainAudio %s (perm %s), speech %s (%s), phone %s, turns %s\n",
		AirPlayEntityToString( inState->screen ), AirPlayEntityToString( inState->permScreen ),
		AirPlayEntityToString( inState->mainAudio ), AirPlayEntityToString( inState->permMainAudio),
		AirPlayEntityToString( inState->speech.entity ), AirPlaySpeechModeToString( inState->speech.mode ),
		AirPlayEntityToString( inState->phoneCall ), AirPlayEntityToString( inState->turnByTurn ) );

	if(gAppStubDelegate.airPlayModesChanged_f!=NULL){
		gAppStubDelegate.airPlayModesChanged_f( inState->screen, inState->permScreen, inState->mainAudio, inState->permMainAudio,
				inState->speech.entity, inState->speech.mode, inState->phoneCall, inState->turnByTurn );
	}

}

//===========================================================================================================================
//	_AirPlayHandleInitialModes
//===========================================================================================================================

static void
	_AirPlayHandleInitialModes(
		AirPlayReceiverSessionRef 	inSession,
		CFDictionaryRef				inParams,
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

	app_ulog( kLogLevelNotice, "_AirPlayHandleInitialModes inParams = %@\n",inParams);

	OSStatus			err;
	CFIndex				i, n;
	CFArrayRef			array;
	CFDictionaryRef		dict;
	int					x;
	Boolean            state;
	int64_t priority,takeConstraint,borrowConstraint,transferType;
	CFStringRef borrowID;
	int64_t permanentEntity;

	array = CFDictionaryGetCFArray( inParams, CFSTR( kAirPlayKey_AppStates ), NULL );
	app_ulog( kLogLevelNotice, "_AirPlayHandleInitialModes initialModes AppStates = %@ \n",array );
	n = array ? CFArrayGetCount( array ) : 0;
	app_ulog( kLogLevelNotice, "appStates = %d \n", n );
	for( i = 0; i < n; ++i )
	{
		dict = CFArrayGetCFDictionaryAtIndex( array, i, &err );
		require_noerr( err, exit );

		x = (int) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_AppStateID ), NULL );
		app_ulog( kLogLevelNotice, "appState[%d] appStateID: %s \n", i, AirPlayAppStateIDToString(x));
		switch( x )
		{
			case kAirPlayAppStateID_PhoneCall:
				state =  CFDictionaryGetBoolean( dict, CFSTR( kAirPlayKey_State ), &err );
				require_noerr( err, exit );
				app_ulog( kLogLevelNotice, "appState[%d] state: %d \n", i, state);
				gAirPlayModeStateRaw.phoneCall = state;
				break;

			case kAirPlayAppStateID_Speech:
				x = (int) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_SpeechMode ), &err );
				require_noerr( err, exit );
				app_ulog( kLogLevelNotice, "appState[%d] speechMode: %d \n", i, x);
				gAirPlayModeStateRaw.speech.entity = state;
				break;

			case kAirPlayAppStateID_TurnByTurn:
				state =  CFDictionaryGetBoolean( dict, CFSTR( kAirPlayKey_State ), &err );
				require_noerr( err, exit );
				app_ulog( kLogLevelNotice, "appState[%d] state: %d \n", i, state);
				gAirPlayModeStateRaw.turnByTurn = state;
				break;

			case kAirPlayAppStateID_NotApplicable:
				break;

			default:
				app_ulog( kLogLevelNotice, "### Ignoring unknown app state %@\n", dict );
				break;
		}
	}

	// Resources

	array = CFDictionaryGetCFArray( inParams, CFSTR( kAirPlayKey_Resources ), NULL );
	n = array ? CFArrayGetCount( array ) : 0;
	app_ulog( kLogLevelNotice, "resources = %d \n", n );
	for( i = 0; i < n; ++i )
	{
		dict = CFArrayGetCFDictionaryAtIndex( array, i, &err );
		require_noerr( err, exit );

		x = (AirPlayAppStateID) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_ResourceID ), NULL );
		//resource[0] resourceID: mainScreen
		app_ulog( kLogLevelNotice, "resource[%d] resourceID: %s \n", i, AirPlayResourceIDToString(x));
		switch( x )
		{
			case kAirPlayResourceID_MainScreen:
				transferType = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferType ), &err );
				priority = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferPriority ), &err );
				takeConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TakeConstraint ), &err );
				borrowConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_BorrowConstraint ), &err );
				if (CFDictionaryContainsKey(dict, CFSTR(kAirPlayKey_BorrowID))) {
					borrowID = CFDictionaryGetCFString( dict, CFSTR( kAirPlayKey_BorrowID ), &err);
				}
				//resource[0] transferType: take
				//resource[0] transferPriority: niceToHave
				//resource[0] takeConstraint: anytime
				//resource[0] borrowConstraint: anytime
				app_ulog( kLogLevelNotice, "resource[%d] transferType: %s \n", i, AirPlayTransferTypeToString(transferType));
				app_ulog( kLogLevelNotice, "resource[%d] transferPriority: %s \n", i, AirPlayTransferPriorityToString(priority));
				app_ulog( kLogLevelNotice, "resource[%d] takeConstraint: %s \n", i, AirPlayConstraintToString(takeConstraint));
				app_ulog( kLogLevelNotice, "resource[%d] borrowConstraint: %s \n", i, AirPlayConstraintToString(borrowConstraint));
				if(/* kAirPlayTransferType_Take == transferType || */kAirPlayTransferType_Borrow == transferType )
				{
					gAirPlayModeStateRaw.screen = kAirPlayEntity_Accessory;
					gAirPlayModeStateRaw.initalScreenAccessory = 1;
					if( borrowID != NULL )
					{
						//uiNotification
						if ((CFStringCompare( borrowID,  CFSTR("bydscreen"), 0) == kCFCompareEqualTo)||(CFStringCompare( borrowID,  CFSTR("uiNotification"), 0) == kCFCompareEqualTo))
						{
							app_ulog( kLogLevelNotice, "resource bydscreen.\n");
							gAirPlayModeStateRaw.initalScreenAccessory = 0;
						}
					}


				}
				gInitialModeChangesRaw.screen.takeConstraint = takeConstraint;
				gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint = borrowConstraint;
				break;

			case kAirPlayResourceID_MainAudio:
				transferType = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferType ), &err );
				priority = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferPriority ), &err );
				takeConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TakeConstraint ), &err );
				borrowConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_BorrowConstraint ), &err );
				app_ulog( kLogLevelNotice, "resource[%d] transferType: %s \n", i, AirPlayTransferTypeToString(transferType));
				app_ulog( kLogLevelNotice, "resource[%d] transferPriority: %s \n", i, AirPlayTransferPriorityToString(priority));
				app_ulog( kLogLevelNotice, "resource[%d] takeConstraint: %s \n", i, AirPlayConstraintToString(takeConstraint));
				app_ulog( kLogLevelNotice, "resource[%d] borrowConstraint: %s \n", i, AirPlayConstraintToString(borrowConstraint));
				if( kAirPlayTransferType_Take == transferType )
				{
					gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Accessory;
				}
				break;

			case kAirPlayResourceID_NotApplicable:
				break;

			default:
				app_ulog( kLogLevelNotice, "### Ignoring unknown resource state %@\n", dict );
				break;
		}
	}

	// initialPermanentEntity

	array = CFDictionaryGetCFArray( inParams, CFSTR( kAirPlayKey_InitialPermanentEntity ), NULL );
	n = array ? CFArrayGetCount( array ) : 0;
	app_ulog( kLogLevelNotice, "initialPermanentEntity = %d \n", n );
	for( i = 0; i < n; ++i )
	{
		dict = CFArrayGetCFDictionaryAtIndex( array, i, &err );
		require_noerr( err, exit );

		x = (AirPlayAppStateID) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_ResourceID ), NULL );
		switch( x )
		{
			case kAirPlayResourceID_MainScreen:
				permanentEntity = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_PermanentEntity ), &err );
				app_ulog( kLogLevelNotice, "###InitPermanentEntity MainScreen %s\n", AirPlayEntityToString(permanentEntity) );
				gAirPlayModeStateRaw.permScreen = permanentEntity;
				break;

			case kAirPlayResourceID_MainAudio:
				permanentEntity = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_PermanentEntity ), &err );
				app_ulog( kLogLevelNotice, "###InitPermanentEntity MainAudio %s\n", AirPlayEntityToString(permanentEntity) );
				gAirPlayModeStateRaw.permMainAudio = permanentEntity;
				break;

			case kAirPlayResourceID_NotApplicable:
				break;

			default:
				app_ulog( kLogLevelNotice, "### Ignoring unknown PermanentEntity state %@\n", dict );
				break;
		}
	}

	
exit:
	return;
}

//===========================================================================================================================
//	_AirPlayHandleChangeModes
//===========================================================================================================================

static void
	_AirPlayHandleChangeModes(
		AirPlayReceiverSessionRef 	inSession,
		CFDictionaryRef				inParams,
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

	app_ulog( kLogLevelNotice, "_AirPlayHandleChangeModesinParams = %@\n",inParams);

//	const char *				inScreenBorrowID;
	AirPlayTransferType 		inScreenType;
	AirPlayTransferPriority		inScreenPriority;
	AirPlayConstraint			inScreenTake;
	AirPlayConstraint			inScreenBorrow;
//	const char *				inAudioBorrowID;
	AirPlayTransferType 		inAudioType;
	AirPlayTransferPriority		inAudioPriority;
	AirPlayConstraint			inAudioTake;
	AirPlayConstraint			inAudioBorrow;

	AirPlayTriState				inPhone;
	AirPlaySpeechMode			inSpeech;
	AirPlayTriState				inTurnByTurn;
	AirPlayEntity				inScreenPermanentEntity;
	AirPlayConstraint			inScreenPermanentTakeConstraint;
	AirPlayConstraint			inScreenPermanentBorrowConstraint;
	AirPlayEntity				inAudioPermanentEntity;
	AirPlayConstraint			inAudioPermanentTakeConstraint;
	AirPlayConstraint			inAudioPermanentBorrowConstraint;

	OSStatus			err;
	CFIndex				i, n;
	CFArrayRef			array;
	CFDictionaryRef		dict;
	int					x;
	Boolean            state;
	char inScreenBorrowID[100] = { 0 };
	char inAudioBorrowID[100] = { 0 };
	int64_t priority = 0;
	int64_t takeConstraint = 0;
	int64_t borrowConstraint = 0;
	int64_t unborrowConstraint = 0;
	int64_t transferType = 0;
	int64_t permanentEntity = 0;
	void * hasTransferPriority = NULL;
	void * hasTakeConstraint = NULL;
	void * hasBorrowConstraint = NULL;
	void * hasUnborrowConstraint = NULL;
	CFStringRef cfStyleScreenBorrowID = NULL;

	array = CFDictionaryGetCFArray( inParams, CFSTR( kAirPlayKey_AppStates ), NULL );
	app_ulog( kLogLevelNotice, "_AirPlayHandleChangeModes  AppStates = %@ \n",array );
	n = array ? CFArrayGetCount( array ) : 0;
	app_ulog( kLogLevelNotice, "appStates = %d \n", n );
	for( i = 0; i < n; ++i )
	{
		dict = CFArrayGetCFDictionaryAtIndex( array, i, &err );
		require_noerr( err, exit );

		x = (int) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_AppStateID ), NULL );
		app_ulog( kLogLevelNotice, "appState[%d] appStateID: %s \n", i, AirPlayAppStateIDToString(x));
		switch( x )
		{
			case kAirPlayAppStateID_PhoneCall:
				state =  CFDictionaryGetBoolean( dict, CFSTR( kAirPlayKey_State ), &err );
				require_noerr( err, exit );
				app_ulog( kLogLevelNotice, "appState[%d] state: %d \n", i, state);
				inPhone = state;
				break;

			case kAirPlayAppStateID_Speech:
				x = (int) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_SpeechMode ), &err );
				require_noerr( err, exit );
				app_ulog( kLogLevelNotice, "appState[%d] speechMode: %d \n", i, x);
				inSpeech = x;
				break;

			case kAirPlayAppStateID_TurnByTurn:
				state =  CFDictionaryGetBoolean( dict, CFSTR( kAirPlayKey_State ), &err );
				require_noerr( err, exit );
				app_ulog( kLogLevelNotice, "appState[%d] state: %d \n", i, state);
				inTurnByTurn = state;
				break;

			case kAirPlayAppStateID_NotApplicable:
				break;

			default:
				app_ulog( kLogLevelNotice, "### Ignoring unknown app state %@\n", dict );
				break;
		}
	}

	// Resources

	array = CFDictionaryGetCFArray( inParams, CFSTR( kAirPlayKey_Resources ), NULL );
	app_ulog( kLogLevelNotice, "_AirPlayHandleChangeModes  Resources = %@ \n",array );
	n = array ? CFArrayGetCount( array ) : 0;
	app_ulog( kLogLevelNotice, "resources = %d \n", n );
	for( i = 0; i < n; ++i )
	{
		dict = CFArrayGetCFDictionaryAtIndex( array, i, &err );
		require_noerr( err, exit );

		x = (AirPlayAppStateID) CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_ResourceID ), NULL );
		//resource[0] resourceID: mainScreen
		app_ulog( kLogLevelNotice, "resource[%d] resourceID: %s \n", i, AirPlayResourceIDToString(x));
		switch( x )
		{
			case kAirPlayResourceID_MainScreen:
				gAirPlayModeStateRaw.initalScreenAccessory = 0; //还原初始化屏幕焦点
				transferType = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferType ), &err );
				hasTransferPriority = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_TransferPriority ));
				if( hasTransferPriority )
				{
					priority = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferPriority ), &err );
				}
				hasTakeConstraint = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_TakeConstraint ));
				if( hasTakeConstraint )
				{
					takeConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TakeConstraint ), &err );
				}
				hasBorrowConstraint = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_BorrowConstraint ));
				if( hasBorrowConstraint )
				{
					borrowConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_BorrowConstraint ), &err );
				}
				hasUnborrowConstraint = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_UnborrowConstraint ));
				if( hasUnborrowConstraint )
				{
					unborrowConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_UnborrowConstraint ), &err );
				}
				if( transferType == kAirPlayTransferType_Borrow || transferType == kAirPlayTransferType_Unborrow )
				{
					CFDictionaryGetCString( dict, CFSTR( kAirPlayKey_BorrowID ), inScreenBorrowID, 100, NULL );
					app_ulog( kLogLevelNotice, "resource[%d] inScreenBorrowID: %s \n", i, inScreenBorrowID);
					cfStyleScreenBorrowID = CFStringCreateWithCString( kCFAllocatorDefault, inScreenBorrowID, kCFStringEncodingUTF8 );
				}
				//resource[0] transferType: take
				//resource[0] transferPriority: niceToHave
				//resource[0] takeConstraint: anytime
				//resource[0] borrowConstraint: anytime
				app_ulog( kLogLevelNotice, "resource[%d] transferType: %s \n", i, AirPlayTransferTypeToString(transferType));
				app_ulog( kLogLevelNotice, "resource[%d] transferPriority: %s \n", i, AirPlayTransferPriorityToString(priority));
				app_ulog( kLogLevelNotice, "resource[%d] takeConstraint: %s \n", i, AirPlayConstraintToString(takeConstraint));
				app_ulog( kLogLevelNotice, "resource[%d] borrowConstraint: %s \n", i, AirPlayConstraintToString(borrowConstraint));
				inScreenType = transferType;
				inScreenPriority= priority;
				inScreenTake = takeConstraint;
				inScreenBorrow = borrowConstraint;
				if(kAirPlayTransferType_Take == inScreenType || kAirPlayTransferType_Borrow == inScreenType)
				{
					//屏幕所有权转移到了车机
					app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Accessory \n" );
					//先检查是否transfer succeed
					if( inScreenType == kAirPlayTransferType_Take )
					{
						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Accessory Take screen.takeConstraint = %d \n", gInitialModeChangesRaw.screen.takeConstraint);
						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Accessory Take inScreenTransferPriority = %d \n",inScreenPriority);
						//Take: takeConstraint and borrowConstraint
						if(inScreenPriority < gInitialModeChangesRaw.screen.takeConstraint)
						{
							app_ulog( kLogLevelNotice, "###_TimingMachine MainScreen ownership transfer to Accessory Take failed \n" );
							gAirPlayModeStateRaw.transferSucceeded = 0;
							return;
						}
						borrowScreenCount = 0;
					}
					else if( inScreenType == kAirPlayTransferType_Borrow )
					{

						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Accessory Borrow screen.borrowOrUnborrowConstraint = %d \n", gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint);
						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Accessory Borrow inScreenTransferPriority = %d \n",inScreenPriority);
						++borrowScreenCount;
					}

					gAirPlayModeStateRaw.transferSucceeded = 1;

					if( inScreenType == kAirPlayTransferType_Take )
					{
						//Take: takeConstraint and borrowConstraint
						gInitialModeChangesRaw.screen.takeConstraint = takeConstraint;
						gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint = borrowConstraint;
					}
					else if( inScreenType == kAirPlayTransferType_Borrow )
					{
						//Borrow: unborrowConstraint
						gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint = unborrowConstraint;
					}

					//切到后台了,则停止录屏
					gAirPlayModeStateRaw.screen   = kAirPlayEntity_Accessory;

					if(kAirPlayTransferType_Take == inScreenType){
						gAirPlayModeStateRaw.permScreen   = kAirPlayEntity_Accessory;
						app_ulog( kLogLevelNotice, "2223 ChangeModes AirPlayModeChanges screen are different kAirPlayTransferType_Take \n");
					}
					//需要回复modesChanged
					gInitialModeChangesRaw.screen.type = inScreenType;
		//			memcpy( &gInitialModeChangesRaw, inChanges, sizeof( AirPlayModeChanges ) );
		//			CarPlayControlClientModesChanged();
		//			CarPlayControlClientTearDownScreen();
		//			CarPlayControlClientGotoBackground();
					//20240125
					CarPlayControlClientChangeModesCommandResponse();
					CarPlayControlClientTearDownScreen();
				}
				else if(kAirPlayTransferType_Untake == inScreenType || kAirPlayTransferType_Unborrow == inScreenType)
				{
					//切到前台,屏幕所有权转移到了手机盒子
					app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device inScreenType = %d \n",inScreenType );
					//先检查是否transfer succeed
					if( kAirPlayTransferType_Unborrow == inScreenType )
					{
						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device Unborrow borrowOrUnborrowConstraint = %d \n",gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint);
						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device Unborrow inScreenTransferPriority = %d \n",inScreenPriority);

						if( inScreenPriority != kAirPlayEntity_NotApplicable && inScreenPriority < gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint )
						{
							app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device Unborrow failed.\n");
							gAirPlayModeStateRaw.transferSucceeded = 0;
							return;
						}
						//判读borrowID
						//uiNotification
						if ( cfStyleScreenBorrowID != NULL && CFStringCompare( cfStyleScreenBorrowID,  CFSTR("uiNotification"), 0) == kCFCompareEqualTo)
						{
							app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device Unborrow failed.\n");
							gAirPlayModeStateRaw.transferSucceeded = 0;
							return;
						}
						--borrowScreenCount;
					}


					gAirPlayModeStateRaw.transferSucceeded = 1;
					if( kAirPlayTransferType_Unborrow == inScreenType )
					{
						gInitialModeChangesRaw.screen.type = inScreenType;
					}
					else if(kAirPlayTransferType_Untake == inScreenType){
						borrowScreenCount = 0;
						gInitialModeChangesRaw.screen.type = inScreenType;
						gAirPlayModeStateRaw.permScreen = kAirPlayEntity_Controller;
						app_ulog( kLogLevelNotice, "ChangeModes AirPlayModeChanges screen are different kAirPlayTransferType_Untake \n");
					}
					int notifyResumeScreen = 0;
					app_ulog( kLogLevelNotice, "ChangeModes AirPlayModeChanges unborrow action borrowScreenCount = %d \n",borrowScreenCount);

					if( gAirPlayModeStateRaw.screen != kAirPlayEntity_Controller && borrowScreenCount <= 0)
					{
						borrowScreenCount = 0;
						gInitialModeChangesRaw.screen.takeConstraint = kAirPlayTransferType_NotApplicable;
						gInitialModeChangesRaw.screen.borrowOrUnborrowConstraint = kAirPlayTransferType_NotApplicable;
						//从后台切换回前台
						app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device,from background --> foreground \n" );
						notifyResumeScreen = 1;
						gAirPlayModeStateRaw.screen = kAirPlayEntity_Controller;
					}
					//需要回复modesChanged
					CarPlayControlClientModesChanged();

					if( notifyResumeScreen == 1)
					{
						CarPlayControlClientResumeScreen();
					}
				}
				break;

			case kAirPlayResourceID_MainAudio:
				transferType = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferType ), &err );
				if( transferType == kAirPlayTransferType_Borrow || transferType == kAirPlayTransferType_Unborrow )
				{
					CFDictionaryGetCString( dict, CFSTR( kAirPlayKey_BorrowID ), inAudioBorrowID, 100, NULL );
					app_ulog( kLogLevelNotice, "resource[%d] inAudioBorrowID: %s \n", i, inAudioBorrowID);
				}

				hasTransferPriority = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_TransferPriority ));
				if( hasTransferPriority )
				{
					priority = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TransferPriority ), &err );
				}
				hasTakeConstraint = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_TakeConstraint ));
				if( hasTakeConstraint )
				{
					takeConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_TakeConstraint ), &err );
				}
				hasBorrowConstraint = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_BorrowConstraint ));
				if( hasBorrowConstraint )
				{
					borrowConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_BorrowConstraint ), &err );
				}
				hasUnborrowConstraint = CFDictionaryGetValue(dict, CFSTR( kAirPlayKey_UnborrowConstraint ));
				if( hasUnborrowConstraint )
				{
					unborrowConstraint = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_UnborrowConstraint ), &err );
				}

				app_ulog( kLogLevelNotice, "resource[%d] transferType: %s \n", i, AirPlayTransferTypeToString(transferType));
				app_ulog( kLogLevelNotice, "resource[%d] transferPriority: %s \n", i, AirPlayTransferPriorityToString(priority));
				app_ulog( kLogLevelNotice, "resource[%d] takeConstraint: %s \n", i, AirPlayConstraintToString(takeConstraint));
				app_ulog( kLogLevelNotice, "resource[%d] borrowConstraint: %s \n", i, AirPlayConstraintToString(borrowConstraint));
				inAudioType = transferType;
				inAudioPriority = priority;
				inAudioTake = takeConstraint;
				inAudioBorrow = borrowConstraint;

				if( mediaButtonSource != NULL )
				{
					dispatch_source_forget( &mediaButtonSource );
				}
				app_ulog( kLogLevelNotice, "ChangeModes  mainAudio.type are different \n");
				if(kAirPlayTransferType_Take == inAudioType || kAirPlayTransferType_Borrow == inAudioType)
				{
					//先检查是否transfer succeed
					if( inAudioType == kAirPlayTransferType_Take )
					{
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory Take audio.takeConstraint = %d \n", gInitialModeChangesRaw.mainAudio.takeConstraint);
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory Take inAudioTransferPriority = %d \n",inAudioPriority);
						//Take: takeConstraint and borrowConstraint
						if(inAudioPriority < gInitialModeChangesRaw.mainAudio.takeConstraint)
						{
							app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory Take failed.\n");
							gAirPlayModeStateRaw.transferSucceeded = 0;
							return;
						}
					}
					else if( inAudioType == kAirPlayTransferType_Borrow )
					{
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory Borrow audio.borrowOrUnborrowConstraint = %d \n", gInitialModeChangesRaw.mainAudio.borrowOrUnborrowConstraint);
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory Borrow inAudioTransferPriority = %d \n",inAudioPriority);
						if(inAudioPriority < gInitialModeChangesRaw.mainAudio.borrowOrUnborrowConstraint)
						{
							app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory Borrow failed.\n");
							gAirPlayModeStateRaw.transferSucceeded = 0;
							return;
						}
					}
					gAirPlayModeStateRaw.transferSucceeded = 1;

					//音频焦点所有权转移到了车机
					app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Accessory \n" );
					//声音焦点被抢走了,则停止声音
					gAirPlayModeStateRaw.mainAudio   = kAirPlayEntity_Accessory;
					if(kAirPlayTransferType_Take == inAudioType)
					{
						gAirPlayModeStateRaw.permMainAudio   = kAirPlayEntity_Accessory;
						app_ulog( kLogLevelNotice, "2288 ChangeModes AirPlayModeChanges audio are different kAirPlayTransferType_Take \n");
					}

		//
		//			//需要回复modesChanged
					gInitialModeChangesRaw.mainAudio.type = inAudioType;
		////			memcpy( &gInitialModeChangesRaw, inChanges, sizeof( AirPlayModeChanges ) );
		//			CarPlayControlClientModesChanged();
					if(gAppStubDelegate.airPlayModesChanged_f != NULL){
//						gAppStubDelegate.airPlayModesChanged_f( inState->screen, inState->permScreen, kAirPlayEntity_Accessory, inState->permMainAudio,
//								inState->speech.entity, inState->speech.mode, inState->phoneCall, inState->turnByTurn );
						gAppStubDelegate.airPlayModesChanged_f( gAirPlayModeStateRaw.screen, gAirPlayModeStateRaw.permScreen, kAirPlayEntity_Accessory,gAirPlayModeStateRaw.permMainAudio,
								gInitialModeChangesRaw.speech,
								gInitialModeChangesRaw.speech, gInitialModeChangesRaw.phoneCall,gInitialModeChangesRaw.turnByTurn );
					}

					if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL ){
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 127, 0 );
					}
				}
				else if(kAirPlayTransferType_Untake == inAudioType ||kAirPlayTransferType_Unborrow == inAudioType)
				{
					//音频焦点切换到了CarPlay,所有权转移到了手机盒子

					//先检查是否transfer succeed
					if( kAirPlayTransferType_Unborrow == inAudioType )
					{
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Device Unborrow audio.borrowOrUnborrowConstraint = %d \n", gInitialModeChangesRaw.mainAudio.borrowOrUnborrowConstraint );
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Device Unborrow inAudioTransferPriority = %d \n",inAudioPriority);

						if( inAudioPriority != kAirPlayEntity_NotApplicable && inAudioPriority < gInitialModeChangesRaw.mainAudio.borrowOrUnborrowConstraint ){
							app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Device Unborrow failed.");
							gAirPlayModeStateRaw.transferSucceeded = 0;
							return;
						}
					}

					gAirPlayModeStateRaw.transferSucceeded = 1;
					app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Device \n" );
					int notifyResumeAudio = 0;
					if( gAirPlayModeStateRaw.mainAudio != kAirPlayEntity_Controller)
					{
						//声音焦点从原车其他音频切换到了CarPlay
						app_ulog( kLogLevelNotice, "MainAudio ownership transfer to Device,from background --> foreground \n" );
						notifyResumeAudio = 1;
					}
					gAirPlayModeStateRaw.mainAudio   = kAirPlayEntity_Controller;
					if(kAirPlayTransferType_Untake == inAudioType)
					{
						gAirPlayModeStateRaw.permMainAudio   = kAirPlayEntity_Controller;
						app_ulog( kLogLevelNotice, "2319 ChangeModes AirPlayModeChanges audio are different kAirPlayTransferType_Untake \n");
					}


					//需要回复modesChanged
					gInitialModeChangesRaw.mainAudio.type = inAudioType;
		//			memcpy( &gInitialModeChangesRaw, inChanges, sizeof( AirPlayModeChanges ) );
					CarPlayControlClientModesChanged();

					if( notifyResumeAudio == 1)
					{
						//发串口命令给应用
						if(gAppStubDelegate.airPlayModesChanged_f != NULL){
//							gAppStubDelegate.airPlayModesChanged_f( inState->screen, inState->permScreen, kStubAirPlayEntity_Controller, inState->permMainAudio,
//									inState->speech.entity, inState->speech.mode, inState->phoneCall, inState->turnByTurn );
							gAppStubDelegate.airPlayModesChanged_f( gAirPlayModeStateRaw.screen, gAirPlayModeStateRaw.permScreen, kStubAirPlayEntity_Controller,gAirPlayModeStateRaw.permMainAudio,
									gInitialModeChangesRaw.speech,
									gInitialModeChangesRaw.speech, gInitialModeChangesRaw.phoneCall,gInitialModeChangesRaw.turnByTurn );
						}
					}

					uint64_t nanos=(int64_t)(1500 * USEC_PER_SEC); //1.5S
					mediaButtonSource = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, eventQueue);
					dispatch_set_context( mediaButtonSource, NULL );
					dispatch_source_set_event_handler_f( mediaButtonSource, _CarPlayControlChangeModeTimer );
					dispatch_source_set_timer( mediaButtonSource, dispatch_time( DISPATCH_TIME_NOW, nanos ), nanos, nanos );
					dispatch_resume( mediaButtonSource );
				}
				break;

			case kAirPlayResourceID_NotApplicable:
				break;

			default:
				app_ulog( kLogLevelNotice, "### Ignoring unknown resource state %@\n", dict );
				break;
		}
	}

	//这里是逻辑处理
	AirPlayModeChanges      changes;
    if (gAirPlaySession) {
        app_ulog(kLogLevelNotice, "\n Change Mode (car -> box):"
                 "\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
                 "\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
                 "\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake,
                 inScreenBorrow, inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech,
                 inTurnByTurn);
        // changeModes (car -> iOS)
        _setChangeModesStruct(&changes, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
                              inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn);
    }
	static AirPlayModeChanges oldChanges;

#if 0
    static AirPlayModeState oldState;
    if (oldState.screen        == inState->screen &&
            oldState.permScreen     == inState->permScreen &&
            oldState.mainAudio      == inState->mainAudio &&
            oldState.permMainAudio  == inState->permMainAudio &&
            oldState.speech.entity  == inState->speech.entity &&
            oldState.speech.mode    == inState->speech.mode &&
            oldState.phoneCall      == inState->phoneCall &&
            oldState.turnByTurn     == inState->turnByTurn) {
        return;
    }
    memcpy(&oldState, inState, sizeof(AirPlayModeState));
    app_ulog(kLogLevelNotice,
             "Modes changed: screen %s (perm %s), mainAudio %s (perm %s), speech %s (%s), phone %s, turns %s\n",
             AirPlayEntityToString(inState->screen), AirPlayEntityToString(inState->permScreen),
             AirPlayEntityToString(inState->mainAudio), AirPlayEntityToString(inState->permMainAudio),
             AirPlayEntityToString(inState->speech.entity), AirPlaySpeechModeToString(inState->speech.mode),
             AirPlayEntityToString(inState->phoneCall), AirPlayEntityToString(inState->turnByTurn));
#endif

exit:
	return;
}

//===========================================================================================================================
//	_AirPlayHandleGetModesChanged
//===========================================================================================================================

static AirPlayModeChanges *
	_AirPlayHandleGetModesChanged(
		AirPlayReceiverSessionRef 	inSession,
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

	app_ulog( kLogLevelNotice, "_AirPlayHandleGetModesChanged.\n");

	return &gInitialModeChangesRaw;
//    handleSessionModesChanged( inState->screen, inState->permScreen, inState->mainAudio, inState->permMainAudio,
//		inState->speech.entity, inState->speech.mode, inState->phoneCall, inState->turnByTurn );
}

//===========================================================================================================================
//	_AirPlayHandleGetAirPlayModeState
//===========================================================================================================================

static AirPlayModeChanges *
	_AirPlayHandleGetAirPlayModeState(
		AirPlayReceiverSessionRef 	inSession,
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

//	app_ulog( kLogLevelNotice, "_AirPlayHandleGetAirPlayModeState.\n");

	return &gAirPlayModeStateRaw;
}

//===========================================================================================================================
//	_AirPlayHandleGetUsbVidPid
//===========================================================================================================================

static void
	_AirPlayHandleGetUsbVidPid(
		AirPlayReceiverSessionRef 	inSession,
		void *						inVid,
		void *						inPid)
{
	(void) inSession;

	app_ulog( kLogLevelNotice, "_AirPlayHandleGetUsbVidPid.\n");
	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,inVid,inPid,&len);
}

//===========================================================================================================================
//	_AirPlayHandleGetAudioFormat
//===========================================================================================================================

static void *
	_AirPlayHandleGetAudioFormat(
		AirPlayReceiverSessionRef 	inSession,
		uint8_t						audioType)
{
	(void) inSession;

//	app_ulog( kLogLevelNotice, "_AirPlayHandleGetAudioFormat.\n");
	if(audioType == AUDIO_CHANNEL_NAVIGATION)
	{
		return gAltAudioFormats;
	}
	else if(audioType == AUDIO_CHANNEL_MEDIA)
	{
		return gMediaAudioFormats;
	}
	else if(audioType == AUDIO_CHANNEL_PHONE)
	{
		return gTelephoneAudioFormats;
	}
	else if(audioType == AUDIO_CHANNEL_SPEECH)
	{
		return gSpeechAudioFormats;
	}

	return gAltAudioFormats;
}

//===========================================================================================================================
//	_AirPlayHandleGetDisplayInfo
//===========================================================================================================================

static void *
	_AirPlayHandleGetDisplayInfo(
		AirPlayReceiverSessionRef 	inSession,
		int *                       width,
		int *                       height,
		int *                       viewAreaWidth,
		int *                       viewAreaHeight,
		int *                       maxFPS,
		void *						inContext )
{
	(void) inSession;
	(void) inContext;

//	app_ulog( kLogLevelNotice, "_AirPlayHandleGetAirPlayModeState.\n");

	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
	 if( (vid == 0x04da && pid == 0x3210)/**lexus lbx need full screen **/ )
	 {
		*width = gRawVideoWidth;
		*height = gRawVideoHeight;
		*viewAreaWidth = gViewAreaWidth;
		*viewAreaHeight = gViewAreaHeight;
	 }else{
		*width = gVideoWidth;
		*height = gVideoHeight;
		*viewAreaWidth = gVideoWidth;
		*viewAreaHeight = gVideoHeight;
	 }
	*maxFPS = gMaxFPS;

	return &gAirPlayModeStateRaw;
}

//===========================================================================================================================
//	_AirPlayHandleRequestSiri
//===========================================================================================================================

static void	_AirPlayHandleRequestSiri( AirPlayReceiverSessionRef inSession, void *inContext )
{
	app_ulog( kLogLevelNotice, "_AirPlayHandleRequestSiri Request accessory Siri \n");
	if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL ){
		gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1500, 0 );
	}
}
//===========================================================================================================================
//	_AirPlayHandleRequestUI
//===========================================================================================================================

static void	_AirPlayHandleRequestUI( AirPlayReceiverSessionRef inSession, CFStringRef inURL, void *inContext )
{
	const char *urlStr = NULL;

	(void) inSession;
	(void) inContext;

	if( inURL ) {
		urlStr = CFStringGetCStringPtr( inURL, kCFStringEncodingUTF8 );
	}

	app_ulog( kLogLevelNotice, "Request accessory UI: \"%s\"\n", urlStr ? urlStr : "NULL" );
	if( urlStr == NULL || CFEqual( urlStr, CFSTR( kAirPlayCommand_RequestUI ) ))
	{
		usleep(200 * 1000 );
		//需要从后台返回前台
		AirPlayModeChanges oldModeChanges;
		memcpy( &oldModeChanges, &gInitialModeChangesRaw, sizeof( AirPlayModeChanges ) );

		app_ulog( kLogLevelNotice, "_AirPlayHandleRequestUI \n");
		app_ulog( kLogLevelNotice, "_AirPlayHandleRequestUI oldModeChanges.screen.type = %d \n" ,oldModeChanges.screen.type);

//		if( oldModeChanges.screen.type != kAirPlayTransferType_Untake )
//		{

			app_ulog( kLogLevelNotice, "_AirPlayHandleRequestUI screen are different \n");
			//切到前台,屏幕所有权转移到了手机盒子
			app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device \n" );
			int notifyResumeScreen = 0;
			if( gAirPlayModeStateRaw.screen != kAirPlayEntity_Controller)
			{
				//从后台切换回前台
				app_ulog( kLogLevelNotice, "MainScreen ownership transfer to Device,from background --> foreground \n" );
				notifyResumeScreen = 1;
			}
			gAirPlayModeStateRaw.screen   = kAirPlayEntity_Controller;
			gAirPlayModeStateRaw.permScreen   = kAirPlayEntity_Controller;

			//需要回复modesChanged
			gInitialModeChangesRaw.screen.type = kAirPlayTransferType_Untake;
			CarPlayControlClientModesChanged();

			if( notifyResumeScreen == 1)
			{
				CarPlayControlClientResumeScreen();
			}
//		}
	}

}

static void _AirPlayReceiverSessionSendiAPMessage( int length, const unsigned char *ptr )
{
	CFDataRef		data;

	data = CFDataCreate( NULL, ptr, length );

	if( gAirPlaySession )	AirPlayReceiverSessionSendiAPMessage( gAirPlaySession, data, NULL, NULL );
}

int lastButtons = 0;
int lastTX = 0;//,tx,ty
int lastTY = 0;

void *timeoutTouchUpThread(void *arg) {
	while( 1 ){

		uint64_t nanos;
		struct timespec ts;
		ts.tv_sec = 0;
		ts.tv_nsec = 0;
		clock_gettime(CLOCK_REALTIME, &ts);
		nanos = ts.tv_sec;
		nanos *= 1000000000;
		nanos += ts.tv_nsec;
		//1毫秒 = 1000微秒 1微秒 = 1000纳秒
		// 2ms * 1000 *1000
		uint64_t betweenTime = nanos - audiQ3InputStartTime;
		//64442915846 2000000
		if( lastTX != 0 && lastTY != 0 && betweenTime >= ( 100 * 1000 ) )
		{
			gAppStubDelegate.airPlayScreenTouchEvent_f(0,lastButtons,lastTX,lastTY);
			lastTX = 0;
			lastTY = 0;
		}
		usleep(200*1000);
	}
	return NULL;
}

pthread_t   m_TouchTimeoutTid;
pthread_t * m_TouchTimeoutThreadPtr = NULL;



static void _TouchEventObserver()
{
	if( m_TouchTimeoutThreadPtr == NULL  )
	{
		int ret = 0;
		if ((ret = pthread_create(&m_TouchTimeoutTid, NULL, timeoutTouchUpThread, NULL))
				!= 0) {
			app_ulog( kLogLevelNotice, "observe_touch timeout thread create failed!\n");
		} else {
			m_TouchTimeoutThreadPtr = &m_TouchTimeoutTid;
			app_ulog( kLogLevelNotice, "observe_touch timeout thread create successed!\n");
		}
	}
}


#define THRESHOLD_DISTANCE 10000   // 滑动距离阈值，单位像素
#define THRESHOLD_VELOCITY 0.5  // Fling速度阈值，单位像素/毫秒

typedef struct {
    int x;
    int y;
    long timestamp;  // 时间戳，单位毫秒
} TouchPoint;

typedef enum {
    SWIPE_NONE,
    SWIPE_LEFT,
    SWIPE_RIGHT,
    FLING_LEFT,
    FLING_RIGHT
} SwipeDirection;

// 计算滑动方向和是否为Fling
SwipeDirection detectFling(TouchPoint start, TouchPoint end) {
    int deltaX = end.x - start.x;
    int deltaY = end.y - start.y;
    long deltaTime = end.timestamp - start.timestamp;

    if (deltaTime <= 0) {
        return SWIPE_NONE;  // 时间差为0或负值，忽略
    }

    // 计算滑动速度
    double velocity = fabs(deltaX) / deltaTime;  // 速度 = 距离 / 时间

    // 判断滑动方向和Fling事件
    if (deltaX > THRESHOLD_DISTANCE) {
        if (velocity > THRESHOLD_VELOCITY) {
            return FLING_RIGHT;
        } else {
            return SWIPE_RIGHT;
        }
    } else if (deltaX < -THRESHOLD_DISTANCE) {
        if (velocity > THRESHOLD_VELOCITY) {
            return FLING_LEFT;
        } else {
            return SWIPE_LEFT;
        }
    }

    return SWIPE_NONE;
}

static TouchPoint startPoint = {0, 0, 0};
static TouchPoint endPoint = {0, 0, 0};  // 示例：左滑

//===========================================================================================================================
//	_AirPlayHandleSessionControl
//===========================================================================================================================

static OSStatus
	_AirPlayHandleSessionControl(
		AirPlayReceiverSessionRef 	inSession,
 		CFStringRef         		inCommand,
        CFTypeRef           		inQualifier,
        CFDictionaryRef     		inParams,
        CFDictionaryRef *   		outParams,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) inQualifier;
    (void) inParams;
    (void) outParams;
	(void) inContext;

	if( CFEqual( inCommand, CFSTR( kAirPlayCommand_DisableBluetooth ) ) )
    {
		char address[64] = { 0 };

		CFDictionaryGetCString( inParams, CFSTR( kAirPlayKey_DeviceID ), address, 64, NULL );
		app_ulog( kLogLevelNotice, "Disable Bluetooth session control request MAC address: %s\n", address );

//		handleSessionControlDisableBluetooth( address );

		AirPlaySessionControlDisableBluetooth();
    	err = kNoErr;
    }
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_iAPSendMessage ) ) )
	{
//		app_ulog( kLogLevelWarning, "kAirPlayCommand_iAPSendMessage _sendWiFiData inParams = %@ \n" , inParams);
		if( inParams )
		{
			CFDictionaryRef params = CFDictionaryGetValue( inParams, CFSTR( kAirPlayKey_Params ));
			if( params )
			{
				CFDataRef data = (CFDataRef)CFDictionaryGetValue( params, CFSTR( kAirPlayKey_Data ) );
				const char * iAP2Message = CFDataGetBytePtr( data );
				int iAP2MessageLength = CFDataGetLength( data );
				aprintBuffer(iAP2Message, iAP2MessageLength, "iAP2Message: ");
		        if (memcmp(iAP2CheckBuf, iAP2Message, sizeof(iAP2CheckBuf)) == 0)
		        {
		        	if(gAppStubDelegate.requestDisableBluetooth_f != NULL )
		        	{
		        		gAppStubDelegate.requestDisableBluetooth_f( 0 );
//		        		iAP2OverCarPlayTearDown();
		        		usleep(100*1000);
		        	}
		            app_ulog( kLogLevelNotice, "[  Device   ] HU  DETECT over Wi-Fi.\n" );
		            iAP2OverWifiSetup();
		            usleep(100*1000);
		        }
				_sendWiFiData( CFDataGetBytePtr( data ), CFDataGetLength( data ) );
			}

		}
	}
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_RequestViewArea ) ) )
	{
		int index = (int)CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_ViewAreaIndex ), NULL);
		app_ulog( kLogLevelNotice, "Received RequestViewArea change %u\n", index );
	}
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_ChangeUIContext ) ) )
	{
		CFStringRef url = (CFStringRef)CFDictionaryGetValue( inParams, CFSTR( kAirPlayKey_ContextURL ) );
		const char *urlStr = NULL;

		if( url ) {
			urlStr = CFStringGetCStringPtr( url, kCFStringEncodingUTF8 );
			app_ulog( kLogLevelNotice, "Received UI context change for %s\n", urlStr );
		}
	}
	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_HIDSendReport ) ) )
	{
		//首先获取uuid

//		app_ulog( kLogLevelWarning, "2636 Received HIDSendReport %@ \n", inParams );

		char * cstringUid[200];
		CFDictionaryGetCString(inParams,  CFSTR(kAirPlayKey_UUID),cstringUid, sizeof(cstringUid), &err);

//		app_ulog( kLogLevelWarning, "###HIDTouch cstringUid = %s  \n",cstringUid );
//		app_ulog( kLogLevelWarning, "###HIDTouch gCStringTouchUID = %s  \n",gCStringTouchUID );

		if( stricmp( cstringUid, gCStringTouchUID )==0 || stricmp( cstringUid, gCStringTouchUID2 )==0){
//			app_ulog( kLogLevelWarning, "###2335 HIDTouch gCStringTouchUID = %s  \n",gCStringTouchUID );
			//width = 1334 ,height = 882 ,fps = 60
//			app_ulog( kLogLevelWarning, "Received HIDSendReport enter %@ \n", inParams );
			uint8_t         report[ 18 ];
			size_t          reportLen;
			int widthMax  = gWidthLogical;
			int heightMax = gHeightLogical;
			int widthPixels  = gVideoWidth;
			int heightPixels = gVideoHeight;
			CFDictionaryGetData(inParams, CFSTR(kAirPlayKey_HIDReport), report, sizeof(report),&reportLen, NULL);

			if( reportLen == 5 || reportLen == 6 || reportLen == 9 || (gTouchFinger == 1 && reportLen == 12)) //japan suzuki主机是9
			{
				int beginIndex = 0;
				if( reportLen == 6)
				{
					beginIndex = 1;
				}
				uint8_t   buttons = report[ beginIndex ];
				int x,y;
				uint8_t xFill [2];
				uint8_t yFill [2];
				xFill[0] = report[(beginIndex + 1)];
				xFill[1] = report[(beginIndex + 2)];

				yFill[0] = report[(beginIndex + 3)];
				yFill[1] = report[(beginIndex + 4)];

				x = ReadLittle16(xFill);
				y = ReadLittle16(yFill);

				//WriteVideo vid:0x1a96 pid:0x2025
				int tx = (int) (x * widthPixels / widthMax);
				int ty = (int) (y * heightPixels / heightMax);


				uint16_t vid = 0x0000;
				uint16_t pid = 0x0000;
				int len = 2;
				gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
				if( vid == 0x2af8 && pid == 0x0104 ){
					ty -= 30;//MG名爵的需要触摸偏移 Jiangsu Toppower
				}else {
					tx -= gOriginalX;
					ty -= gOriginalY;
				}
				//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#if 0
				if( xqcarplayDebugTouch == 1 )
				{
					char coordinate[100];
					int n = 0, len =0 ;
					n = sprintf( &coordinate[ len ], "%d",x );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], "%d", y );
					len += ( n );

					n = sprintf( &coordinate[ len ], "|" );
					len += ( n );

					n = sprintf( &coordinate[ len ], "%d",0 );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], "%d", 0 );
					len += ( n );

					n = sprintf( &coordinate[ len ], "|" );
					len += ( n );

					n = sprintf( &coordinate[ len ], ",tx =  %d",tx );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], ",ty =  %d",ty );
					len += ( n );

					 __system_property_set("sys.xqcarplay.touch.vehicle.coordinate", coordinate);
				}
#endif
				//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
//				app_ulog( kLogLevelWarning, "Received HIDSendReport button = %d ,x = %d ,y = %d  \n", buttons,tx,ty);
				// lexus vid:0x1d6b pid:0x0101
				char	carName [ 100 ];
				// __system_property_get("sys.shark.oem.car.name", carName);
				Boolean isLEXUS = (vid == 0x1d6b && pid == 0x0101) && (  ( strcmp("LEXUS",carName ) == 0 ) ||  ( strcmp("CAR MULTIMEDIA",carName ) == 0 ) ||  ( strcmp("Family Lexus",carName ) == 0 )   );
				Boolean isAudiQ3S = (vid == 0x0e46 && pid == 0x5349) && ( strcmp("AUDI MMI",carName ) == 0 );
				Boolean ignoreTouchEvent = isLEXUS && ty > 710 && (( gOriginalSafeWidth == 1920 && gOriginalSafeHeight == 720)||( gOriginalSafeWidth == 1280 && gOriginalSafeHeight == 720));
				if(gAppStubDelegate.airPlayScreenTouchEvent_f != NULL && ( !ignoreTouchEvent )){
					//Audi没有抬起事件
					if( isAudiQ3S )
					{
						//启动线程
						lastTX = tx;
						lastTY = ty;

						uint64_t nanos;
						struct timespec ts;
						ts.tv_sec = 0;
						ts.tv_nsec = 0;
						clock_gettime(CLOCK_REALTIME, &ts);
						nanos = ts.tv_sec;
						nanos *= 1000000000;
						nanos += ts.tv_nsec;
						audiQ3InputStartTime = nanos;
						_TouchEventObserver();
					}
					gAppStubDelegate.airPlayScreenTouchEvent_f(0,buttons,tx,ty);
				}

			}else if( reportLen == 12){
				//00 01 02 03 04 05   06 07 08 09 10 11
				//01 01 cc 5e 97 4a   00 01 a9 4c 8c 35
				int x, y;
				uint8_t   finger  = report[ 0 ];
				uint8_t   buttons = report[ 1 ];
				uint8_t xFill [2];
				uint8_t yFill [2];
				xFill[0] = report[2];
				xFill[1] = report[3];

				yFill[0] = report[4];
				yFill[1] = report[5];

				x = ReadLittle16(xFill);
				y = ReadLittle16(yFill);

//				int tx = (int) (x * mPhoneContainerWidth / mContainerWidth);
//				int ty = (int) (y * mPhoneContainerHeight / mContainerHeight);
				int tx = (int) (x * widthPixels / widthMax);
				int ty = (int) (y * heightPixels / heightMax);

//				app_ulog( kLogLevelWarning, "Received HIDSendReport button = %d ,x = %d ,y = %d  \n", buttons,x,y );
//				app_ulog( kLogLevelWarning, "Received HIDSendReport button = %d ,tx = %d ,ty = %d  \n", buttons,tx,ty );

				//==================================================================================================
				int x1, y1;
				uint8_t   finger1  = report[ 6 ];
				uint8_t   buttons1 = report[ 7 ];
				uint8_t x1Fill [2];
				uint8_t y1Fill [2];
				x1Fill[0] = report[8];
				x1Fill[1] = report[9];

				y1Fill[0] = report[10];
				y1Fill[1] = report[11];

				x1 = ReadLittle16(x1Fill);
				y1 = ReadLittle16(y1Fill);

				uint16_t vid = 0x0000;
				uint16_t pid = 0x0000;
				int len = 2;
				//vid:0x042f pid:0x0601 Ford F-150
				int tx1 = (int) (x1 * widthPixels / widthMax);
				int ty1 = (int) (y1 * heightPixels / heightMax);

				tx1 -= gOriginalX;
				ty1 -= gOriginalY;

//				app_ulog( kLogLevelWarning, "Received HIDSendReport button1 = %d ,x1 = %d ,y1 = %d  \n", buttons1,x1,y1 );
//				app_ulog( kLogLevelWarning, "Received HIDSendReport button1 = %d ,tx1 = %d ,ty1 = %d  \n", buttons1,tx1,ty1 );

				//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#if 0
				if( xqcarplayDebugTouch == 1 )
				{
					char coordinate[100];
					int n = 0, len =0 ;
					n = sprintf( &coordinate[ len ], "%d",x );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], "%d", y );
					len += ( n );

					n = sprintf( &coordinate[ len ], "|" );
					len += ( n );

					n = sprintf( &coordinate[ len ], "%d",x1 );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], "%d", y1 );
					len += ( n );

					n = sprintf( &coordinate[ len ], "|" );
					len += ( n );

					n = sprintf( &coordinate[ len ], ",tx =  %d",tx );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], ",ty =  %d",ty );
					len += ( n );

					n = sprintf( &coordinate[ len ], "|" );
					len += ( n );

					n = sprintf( &coordinate[ len ], ",tx1 =  %d",tx1 );
					len += ( n );

					n = sprintf( &coordinate[ len ], "," );
					len += ( n );

					n = sprintf( &coordinate[ len ], ",ty1 =  %d",ty1 );
					len += ( n );

					 __system_property_set("sys.xqcarplay.touch.vehicle.coordinate", coordinate);
				}
#endif
				//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
				//丰田Garmin主机 vid:0x091e pid:0x884a
				if(gAppStubDelegate.airPlayScreenMultiTouchEvent_f != NULL){
						gAppStubDelegate.airPlayScreenMultiTouchEvent_f(0,buttons,tx,ty,buttons1,tx1,ty1);
				}

			}

		}
		//Knob
		if( stricmp( cstringUid, gCStringKnobUID)==0){

			uint8_t         report[ 10 ];
			size_t          reportLen;
			CFDictionaryGetData(inParams, CFSTR(kAirPlayKey_HIDReport), report, sizeof(report),&reportLen, NULL);

		    Boolean   inButton = false;
		    Boolean   inHome = false;
		    Boolean   inBack = false;
		    uint8_t    inX = 0;
		    uint8_t    inY = 0;
		    uint8_t    inWheel = 0;

//		    inButton = report[ 0 ]&0x01;
//		    inHome   = report[ 0 ]&0x02;
//		    inBack   = report[ 0 ]&0x04;

		    app_ulog( kLogLevelWarning, "Received HIDSendReport KnobButtons reportLen = %d \n", reportLen);
		    aprintBuffer(report, reportLen, "KnobButtons: ");

		    if ( reportLen == 2)
		    {
		    	inButton     = report[ 0 ];
			    inWheel      = report[ 1 ]&0xFF;
		    }
		    else if ( reportLen == 3)
		    {
			    inX      = report[ 0 ]&0xFF;
			    inY      = report[ 1 ]&0xFF;
			    inWheel      = report[ 2 ]&0xFF;
		    }
		    else if ( reportLen == 4)
		    {
		    	inButton = report[ 0 ];
			    inX      = report[ 1 ]&0xFF;
			    inY      = report[ 2 ]&0xFF;
			    inWheel      = report[ 3 ]&0xFF;
		    }else {
			    inX      = report[ 1 ]&0xFF;
			    inY      = report[ 2 ]&0xFF;
			    inWheel      = report[ 3 ]&0xFF;
		    }

//			app_ulog( kLogLevelWarning, "Received HIDSendReport inButton = %d ,inHome = %d ,inBack = %d ,inX = %d , inY = %d , inWheel = %d , reportLen = %d \n",
//					inButton,inHome,inBack,
//					inX,inY,inWheel, reportLen
//					);
			if(gAppStubDelegate.airPlayScreenKnobEvent_f!=NULL){
				gAppStubDelegate.airPlayScreenKnobEvent_f(0,inButton,inHome,
						inBack,inX,inY,inWheel);
			}

			if( inX != 127 && inX != 129 && inY != 127 && inY != 129 )
			{
				struct hid_user_data32 hid_user;
				hid_user.data = (__u32)report;
				hid_user.data_len =  reportLen;
				hid_user.user_id =  knobButtonUserId;
				ioctl(gHidDriverFd, HID_SET_DATA, &hid_user);
			}
		}

		//MediaButton
		if( stricmp( cstringUid, gCStringMediaButtonsUID)==0){

			uint8_t         report[ 5 ];
			size_t          reportLen;
			CFDictionaryGetData(inParams, CFSTR(kAirPlayKey_HIDReport), report, sizeof(report),&reportLen, NULL);

			app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons reportLen = %d \n", reportLen);

			uint16_t vid = 0x0000;
			uint16_t pid = 0x0000;
			int len = 2;
			Boolean sendEventDriver = true;
			gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
			// vid:0x0424 pid:0x4920 丰田 Yaris Cross接听挂断键
			if(vid == 0x0424 && pid == 0x4920)
			{
				if( reportLen == 2 && HIDYarisCarModel == 1)
				{
					uint8_t inButton = report[ 1 ];
					if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL && inButton == 1){
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 100, 0 );
					}
				}

			}

			if(vid == 0x25e1 && pid == 0x4351)
			{
				if( reportLen == 1)
				{
					uint8_t inButton = report[ 0 ];
					if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f !=NULL){
						if( inButton == 2 )
						{
							gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1400,0);
						}

					}
				}

			}

			// 路虎揽胜 The accessory vid:0x8086 pid:0x9999
			if(vid == 0x8086 && pid == 0x9999)
			{
				if( reportLen == 1)
				{
					uint8_t inButton = report[ 0 ];
					app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons inButton = %d \n", inButton);
					if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL){
						if( inButton == 3 )
						{
							gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1,0);
						}
						else if( inButton == 4 )
						{
							gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 2,0);
						}

					}
				}

			}
			// Carmax Drive 3.0 DA Desay The accessory vid:0x0424 pid:0x4920
			if(vid == 0x0424 && pid == 0x4920)
			{
				if( reportLen == 2)
				{
					uint8_t inButton = report[ 0 ];
					app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons inButton = %d \n", inButton);
					if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL){
						if (inButton & (1 << 4)) {
							gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1, 0);
						}
						if (inButton & (1 << 5)) {
							gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 2, 0);
						}

					}
				}

			}
			// AION 0x2b37 pid:0x096c
			if(vid == 0x2b37 && pid == 0x096c)
			{
				uint8_t inButton = report[ 0 ];
				app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons AION inButton = %d \n", inButton);
				if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL)
				{
					sendEventDriver = false;
					if (inButton & (1 << 2)) {
						//Scan Next Track
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons AION inButton Scan Next Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1, 0);
					}
					if (inButton & (1 << 3)) {
						//Scan Previous Track
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons AION inButton Scan Previous Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 2, 0);
					}
					if (inButton & (1 << 0)) {
						//Play
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons AION inButton Play Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 126, 0);
					}
					if (inButton & (1 << 1)) {
						//Pause
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons AION inButton Pause Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 4, 0);
					}

				}
			}
			// EXEED vid:0x8087 pid:0x0a68
			if(vid == 0x8087 && pid == 0x0a68)
			{
				uint8_t inButton = report[ 0 ];
				app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons EXEED inButton = %d \n", inButton);
				if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL)
				{
					//这是个ArrayInput
//					0x09, 0x00     // Usage (Unassigned)
//					0x09, 0xB0     // Usage (Play)
//					0x09, 0xB1     // Usage (Pause)
//					0x09, 0xB5     // Usage (Next Track)
//					0x09, 0xB6     // Usage (Previous Track)
					//报告 1 个字节的值，这个值是一个 Usage 列表中的索引

					if (inButton == 3 ) {
						//Scan Next Track
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons EXEED inButton Scan Next Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1, 0);
					}
					if (inButton == 4) {
						//Scan Previous Track
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons EXEED inButton Scan Previous Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 2, 0);
					}
					if (inButton == 1) {
						//Play
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons EXEED inButton Play Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 126, 0);
					}
					if (inButton == 2) {
						//Pause
						app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons EXEED inButton Pause Track \n");
						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 4, 0);
					}

				}
			}
			if( sendEventDriver )
			{
				struct hid_user_data32 hid_user;
				hid_user.data = (__u32)report;
				hid_user.data_len =  reportLen;
				hid_user.user_id =  mediaButtonUserId;
				ioctl(gHidDriverFd, HID_SET_DATA, &hid_user);
			}
		}

		if( stricmp( cstringUid, gCStringMediaButtonsUID1)==0){

			uint8_t         report[ 5 ];
			size_t          reportLen;
			CFDictionaryGetData(inParams, CFSTR(kAirPlayKey_HIDReport), report, sizeof(report),&reportLen, NULL);

			app_ulog( kLogLevelWarning, "Received HIDSendReport MediaButtons2 reportLen = %d \n", reportLen);

			struct hid_user_data32 hid_user;
			hid_user.data = (__u32)report;
			hid_user.data_len =  reportLen;
			hid_user.user_id =  mediaButtonUserId2;
			ioctl(gHidDriverFd, HID_SET_DATA, &hid_user);
		}


		//TelephoneButton
		if( stricmp( cstringUid, gCStringTelephonyUID)==0){

			uint8_t         report[ 5 ];
			size_t          reportLen;
			CFDictionaryGetData(inParams, CFSTR(kAirPlayKey_HIDReport), report, sizeof(report),&reportLen, NULL);
			aprintBuffer( report,reportLen,"TelephoneButton: ");

			struct hid_user_data32 hid_user;
			hid_user.data = (__u32)report;
			hid_user.data_len =  reportLen;
			hid_user.user_id =  telephoneButtonUserId;
			ioctl(gHidDriverFd, HID_SET_DATA, &hid_user);

			uint8_t inButton = report[ 0 ];
			if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL ){
				// vid:0x1234 pid:0x1111
				uint16_t vid = 0x0000;
				uint16_t pid = 0x0000;
				int len = 2;
				gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
				//0x2259 pid:0x0002
				if(vid == 0x2259 && pid == 0x0002)
				{
					app_ulog( kLogLevelWarning, "Received wheel keyEvent inButton: %d \n", inButton );
					//香港木马人
					if (inButton == 1) {
						//接听键
						inButton = 100;
					}
					else if (inButton == 4) {
						//挂断键
						inButton = 101;
					}
					gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, inButton, 0 );
				}
				else if(vid == 0x1004 && pid == 0x7714)
				{
					//越南ford ranger 接听键没作用
					if (inButton == 3) {
						//接听键
						inButton = 100;
					}
					gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, inButton, 0 );
				}
				else if(vid == 0x06d3 && pid == 0x6040) // 0x06d3 pid:0x6040
				{
					//本田honda
					//接听bit0
					uint8_t hook = (inButton & 0x01);
					uint8_t hangup = ((inButton & 0x04) >> 2);
					app_ulog( kLogLevelWarning, "Received wheel keyEvent hook: %d \n", hook );
					app_ulog( kLogLevelWarning, "Received wheel keyEvent hangup: %d \n", hangup );
					if (hook == 1) {
						//接听键
						inButton = 100;
					}
					if (hangup == 1) {
						//挂断键
						inButton = 101;
					}
					gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, inButton, 0 );
				}
				else if(vid == 0x08e4 && pid == 0x01b2) // 台湾丰田阿尔法 0x08e4 pid:0x01b2
				{
					app_ulog( kLogLevelWarning, "Received wheel keyEvent 台湾丰田阿尔法. \n" );
					//台湾丰田阿尔法 0x08e4 pid:0x01b2
					//接听bit1
					uint8_t hook = (inButton & 0x02)>>1;
					uint8_t hangup = ((inButton & 0x04) >> 2);
					app_ulog( kLogLevelWarning, "Received wheel keyEvent hook: %d \n", hook );
					app_ulog( kLogLevelWarning, "Received wheel keyEvent hangup: %d \n", hangup );
					if (hook == 1) {
						//接听键
						inButton = 100;
					}
					if (hangup == 1) {
						//挂断键
						inButton = 101;
					}
					gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, inButton, 0 );
				}
				else if((vid == 0x1234 && pid == 0x1111) ||
						(vid == 0x2e7a && pid == 0x0218) )
				{
					if (inButton == 2) {
						//接听键
						inButton = 100;
					}
					else if(inButton == 1 || inButton == 3)
					{
						//挂断键
						inButton = 101;
					}
					gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, inButton, 0 );
				}else {
					if( inButton == 1 )
					{
						//接听键
						if( telephoneButtonCycle == 0 ){
							telephoneButtonCycle = 1;
							inButton = 100;
						} else {
							telephoneButtonCycle = 0;
							inButton = 101;
						}

					}
					else if( inButton == 2 )
					{
						//挂断键
						telephoneButtonCycle = 0;
						inButton = 101;
					}
					gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, inButton, 0 );
				}
			}
		}

		//TouchPad
		if( stricmp( cstringUid, gCStringTouchPadUID)==0 || stricmp( cstringUid, gCStringTouchPadUID2)==0){

			int widthMax  = gTouchPadWidthLogical;
			int heightMax = gTouchPadHeightLogical;
			int widthPixels  = gVideoWidth;
			int heightPixels = gVideoHeight;

			uint8_t         report[ 19 ];
			size_t          reportLen;
			CFDictionaryGetData(inParams, CFSTR(kAirPlayKey_HIDReport), report, sizeof(report),&reportLen, NULL);

			aprintBuffer( report,reportLen,"TouchPad: ");

			//Benze >>>
			if( ofnInputSupport == 1 && reportLen == 5)
			{
				int x = 0;
				int y = 0;
				uint8_t action = 0;
				uint8_t xFill [2];
				uint8_t yFill [2];
				xFill[0] = report[ 1 ];
				xFill[1] = report[ 2 ];

				yFill[0] = report[ 3 ];
				yFill[1] = report[ 4 ];
				action   = report[ 0 ];

				x = ReadBig16(xFill);
				y = ReadBig16(yFill);

				app_ulog( kLogLevelWarning, "Received TouchPad X %d \n", x );
				app_ulog( kLogLevelWarning, "Received TouchPad Y %d \n", y );
				app_ulog( kLogLevelWarning, "Received TouchPad action %d \n", action );

				uint64_t nanos;
				struct timespec ts;
				ts.tv_sec = 0;
				ts.tv_nsec = 0;
				clock_gettime(CLOCK_REALTIME, &ts);
				nanos = ts.tv_sec;
				nanos *= 1000000000;
				nanos += ts.tv_nsec;
				nanos /= 1000000; //转成毫秒

				startPoint.x = x;
				startPoint.y = y;
				startPoint.timestamp = nanos;
				if(endPoint.x != 0 && endPoint.y != 0 && endPoint.timestamp != 0 )
				{
					SwipeDirection direction = detectFling(startPoint, endPoint);
				    // 输出滑动方向
				    switch (direction) {
				        case SWIPE_LEFT:
							//上报上一曲下一曲事件
							app_ulog( kLogLevelWarning, "Detected: Left Swipe.\n" );
//							if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL )
//							{
//								gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 2, 0 );
//							}
//							x = 0;
//							y = 0;
//							nanos = 0;
				            break;
				        case SWIPE_RIGHT:
				            app_ulog( kLogLevelWarning, "Detected: Right Swipe.\n" );
//							if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL )
//							{
//								gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1, 0 );
//							}
//							x = 0;
//							y = 0;
//							nanos = 0;
				            break;
				        case FLING_LEFT:
							app_ulog( kLogLevelWarning, "Detected: Left Fling.\n" );
							if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL )
							{
								gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 2, 0 );
							}
							x = 0;
							y = 0;
							nanos = 0;
				            break;
				        case FLING_RIGHT:
				            app_ulog( kLogLevelWarning, "Detected: Right Fling.\n" );
							if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL )
							{
								gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1, 0 );
							}
							x = 0;
							y = 0;
							nanos = 0;
				            break;
				        default:
				            app_ulog( kLogLevelWarning, "No significant swipe detected.\n" );
				            break;
				    }
				}
				endPoint.x = x;
				endPoint.y = y;
				endPoint.timestamp = nanos;





//				uint64_t nanos;
//				struct timespec ts;
//				ts.tv_sec = 0;
//				ts.tv_nsec = 0;
//				clock_gettime(CLOCK_REALTIME, &ts);
//				nanos = ts.tv_sec;
//				nanos *= 1000000000;
//				nanos += ts.tv_nsec;
//
//				//1毫秒 = 1000微秒 1微秒 = 1000纳秒
//				// 2ms * 1000 *1000
//				uint64_t betweenTime = nanos - ofnInputStartTime;
//				//64442915846 2000000
//				if( nanos - ofnInputStartTime >= ( 2 * 1000 * 1000 ) )
//				{
//					ofnInputStartTime = nanos;
//					ofnInputFlipCount ++;
//				}
//
//				if( ofnInputFlipCount >= 5 )
//				{
//					ofnInputFlipCount = 0;
//					//上报上一曲下一曲事件
//					app_ulog( kLogLevelWarning, "Received TouchPad nextSong1.\n" );
//					if(gAppStubDelegate.airPlayScreenMediaButtonEvent_f != NULL ){
//						gAppStubDelegate.airPlayScreenMediaButtonEvent_f(0, 1, 0 );
//					}
//				}

			}
			//Benze <<<

			struct hid_user_data32 hid_user;
			hid_user.data = (__u32)report;
			hid_user.data_len =  reportLen;
			hid_user.user_id =  touchPadUserId;
			ioctl(gHidDriverFd, HID_SET_DATA, &hid_user);

		}

	}

	else if( CFEqual( inCommand, CFSTR( kAirPlayCommand_TearDownStreams ) ) )
	{
		//首先获取uuid

		app_ulog( kLogLevelWarning, "Received tearDownStreams %@ \n", inParams );
		err = kNoErr;


	}

    else
    {
		const char *commond = NULL;

		commond = CFStringGetCStringPtr( inCommand, kCFStringEncodingUTF8 );
		app_ulog( kLogLevelNotice, "Unsupported session control request: %s\n", commond );
		err = kNoErr;
    }

	return( err );
}

//===========================================================================================================================
//	_AirPlayHandleAudioFocusChanged
//===========================================================================================================================

static OSStatus
	_AirPlayHandleAudioFocusChanged(
		int 						audioType,
        int     					focus )
{
	OSStatus err;

	app_ulog( kLogLevelNotice, "_AirPlayHandleAudioFocusChanged: audioType = %d , focus = %d \n", audioType, focus);

	if( audioType > 0 && audioType != AUDIO_CHANNEL_NAVIGATION && focus)
	{
		gInitialModeChangesRaw.mainAudio.type = kAirPlayTransferType_Untake;
		gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Controller;
	}
	if((audioType == AUDIO_CHANNEL_PHONE || audioType == AUDIO_CHANNEL_SPEECH) && !focus &&  gAppNeedReleaseScreen )
	{
		gAppNeedReleaseScreen = 0;
		//屏幕所有权转移到了车机
		//切到后台了,则停止录屏
		gAirPlayModeStateRaw.screen   = kAirPlayEntity_Accessory;

		//需要回复modesChanged
		gInitialModeChangesRaw.screen.type = kAirPlayTransferType_Take;
		CarPlayControlClientModesChanged();

		CarPlayControlClientTearDownScreen();

	}

	if(gAppStubDelegate.airPlayAudioFocusChanged_f != NULL){
		gAppStubDelegate.airPlayAudioFocusChanged_f(0, audioType, focus);
	}

	return err;
}

//===========================================================================================================================
//	_AirPlayHandleSessionDisplay
//===========================================================================================================================

static OSStatus
	_AirPlayHandleSessionDisplay(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		inParams,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) inParams;
	(void) inContext;

	// displays
	int64_t display_type,display_widthPixels,display_heightPixels,display_uiAppearanceMode;
	int64_t display_primaryInputDevice,display_widthPhysical,display_heightPhysical,display_maxFPS,display_uiAppearanceSetting;
	char* display_uuid[200];

	display_widthPixels  = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_WidthPixels ), &err );
	display_heightPixels = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_HeightPixels ), &err );

	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay widthPixels:%d \n", display_widthPixels );
	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay heightPixels:%d \n", display_heightPixels );

	display_widthPhysical  = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_WidthPhysical ), &err );
	display_heightPhysical = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_HeightPhysical ), &err );

	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay widthPhysical:%d \n", display_widthPhysical );
	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay heightPhysical:%d \n", display_heightPhysical );

//	display_uiAppearanceMode = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_UIAppearanceMode ), &err );
//	display_primaryInputDevice = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_PrimaryInputDevice ), &err );
//	display_uiAppearanceSetting = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_UIAppearanceSetting ), &err );
	display_maxFPS = CFDictionaryGetInt64( inParams, CFSTR( kAirPlayKey_MaxFPS ), &err );
//	CFDictionaryGetCString(inParams,  CFSTR(kAirPlayKey_UUID),display_uuid, sizeof(display_uuid), &err);
	CFDictionaryGetCString(inParams,  CFSTR(kAirPlayKey_UUID),display_uuid, sizeof(display_uuid), &err);
	app_ulog( kLogLevelWarning, "### AppStubkAirPlayStreamType_MainScreen display_uuid:%s \n",display_uuid );
	//  __system_property_set("sys.shark.screen.uuid", display_uuid);

	gRawVideoWidth  = display_widthPixels;
	gRawVideoHeight = display_heightPixels;
	gVideoWidth  = display_widthPixels;
	gVideoHeight = display_heightPixels;
	gViewAreaWidth = display_widthPixels;
	gViewAreaHeight = display_heightPixels;

	gOriginalX = 0;
	gOriginalY = 0;

	gWidthPhysical  = display_widthPhysical;
	gHeightPhysical = display_heightPhysical;

	gMaxFPS = display_maxFPS;

	 int w = 0;
	 int h = 0;
	 int x = 0;
	 int y = 0;
	 w = gVideoWidth;
	 h = gVideoHeight;
	CFArrayRef	viewAreas = CFDictionaryGetCFArray(inParams, CFSTR(kAirPlayKey_ViewAreas), &err);
	uint32_t initialViewArea = CFDictionaryGetInt64(inParams, CFSTR( kAirPlayKey_InitialViewArea ),  &err);
	app_ulog( kLogLevelWarning, "### AppStubkAirPlayStreamType_MainScreen initialViewArea: %d \n",initialViewArea );
	if ( viewAreas )
	{
		size_t viewAreasCount = CFArrayGetCount( viewAreas );
		if( viewAreasCount > 0)
		{
			int index = 0;
			int64_t maxWidth = 0;
			int64_t maxHeight = 0;

			CFDictionaryRef initialDict = CFArrayGetCFDictionaryAtIndex(viewAreas, initialViewArea, &err);
			int64_t initialDictWidth  = CFDictionaryGetInt64( initialDict, CFSTR( kAirPlayKey_WidthPixels ), &err );
			int64_t initialDictHeight = CFDictionaryGetInt64( initialDict, CFSTR( kAirPlayKey_HeightPixels ), &err );

			gViewAreaWidth = initialDictWidth;
			gViewAreaHeight = initialDictHeight;

			for( int i = 0 ; i < viewAreasCount ; i++ )
			{
				CFDictionaryRef dict = CFArrayGetCFDictionaryAtIndex(viewAreas, i, &err);
				app_ulog( kLogLevelWarning, "### AppStubkAirPlayStreamType_MainScreen each viewarea: %@ \n",dict );
				int64_t dictWidth  = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_WidthPixels ), &err );
				int64_t dictHeight = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_HeightPixels ), &err );

				uint16_t vid = 0x0000;
				uint16_t pid = 0x0000;
				int len = 2;
				gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
				char	carName [ 100 ];
				char	carModel [ 100 ];
				// __system_property_get("sys.shark.oem.car.name", carName);
				// __system_property_get("sys.shark.oem.car.model", carModel);
				Boolean isLEXUS = false; // /*(vid == 0x1d6b && pid == 0x0104) && */( strcmp("LEXUS",carName ) == 0 );
				Boolean isToyotaMulti = /*(vid == 0x1d6b && pid == 0x0104) && */( strcmp("CAR MULTIMEDIA",carName ) == 0 );
				Boolean isToyotaCamry = (vid == 0x04da && pid == 0x3210)/* && ( strcmp("CN-SS8EA3AJ",carModel ) == 0 )*/; //Toyota Camry 2024
				Boolean isSeekMax = true;

				// 0x1d6b pid:0x0101
				//LEXUS 0x04da pid:0x3210 ,台湾的LEXUS 0x1d6b pid:0x0101
				//ToyotaCamry:vid:0x04da pid:0x3210
				if((/*(vid == 0x04da && pid == 0x3210) || */(vid == 0x1d6b && pid == 0x0101)) && ( isLEXUS || isToyotaCamry || isToyotaMulti ) && !isSeekMax )
				{
					//20241014越南一款LEXUS左侧有导航栏的
					if(( maxWidth==0 && maxHeight==0 ) || (dictWidth < maxWidth || dictHeight < maxHeight ) )
					{//Tinker找最小分辨率
						maxWidth = dictWidth;
						maxHeight = dictHeight;
						index = i;
						app_ulog( kLogLevelWarning, "### AppStubkAirPlayStreamType_MainScreen each viewarea: Tinker找最小分辨率 width = %d: \n", maxWidth);
						app_ulog( kLogLevelWarning, "### AppStubkAirPlayStreamType_MainScreen each viewarea: Tinker找最小分辨率 height = %d: \n", maxHeight);
					}
				}else{
					if( dictWidth > maxWidth || dictHeight > maxHeight ){
						maxWidth = dictWidth;
						maxHeight = dictHeight;
						index = i;
					}
				}
			}

			if( viewAreasCount > 1 && index != initialViewArea )
			{
				//需要changeViewAreaIndex
				gAirPlayModeStateRaw.transferViewArea = true;
				gAirPlayModeStateRaw.changeViewAreaIndex = index;
			}

			CFDictionaryRef viewAreaDict = CFArrayGetCFDictionaryAtIndex(viewAreas, index, &err);
			if( viewAreaDict )
			{
				CFBooleanRef	boolObj = CFDictionaryGetCFBoolean(viewAreaDict,  CFSTR(kAirPlayKey_ViewAreaTransitionControl), &err);

				w  = CFDictionaryGetInt64( viewAreaDict, CFSTR( kAirPlayKey_WidthPixels ), &err );
				h = CFDictionaryGetInt64( viewAreaDict, CFSTR( kAirPlayKey_HeightPixels ), &err );

				x  = CFDictionaryGetInt64( viewAreaDict, CFSTR( kAirPlayKey_OriginXPixels ), &err );
				y  = CFDictionaryGetInt64( viewAreaDict, CFSTR( kAirPlayKey_OriginYPixels ), &err );

		        gOriginalX = x;
		        gOriginalY = y;

				CFMutableDictionaryRef newSafeArea = CFDictionaryGetValue( viewAreaDict, CFSTR( kAirPlayKey_SafeArea ));

				if( newSafeArea )
				{
			        w = CFDictionaryGetInt64( newSafeArea, CFSTR( kAirPlayKey_WidthPixels ), &err  );
			        h = CFDictionaryGetInt64( newSafeArea, CFSTR( kAirPlayKey_HeightPixels ), &err  );
			        x = CFDictionaryGetInt64( newSafeArea, CFSTR( kAirPlayKey_OriginXPixels ), &err  );
			        y = CFDictionaryGetInt64( newSafeArea, CFSTR( kAirPlayKey_OriginYPixels ), &err  );

			        gOriginalSafeWidth = w;
			        gOriginalSafeHeight = h;

			        gOriginalX = x;
			        gOriginalY = y;
			        app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay w:%d \n", w );
			        app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay h:%d \n", h );
			        app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay x:%d \n", x );
			        app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay y:%d \n", y );
			        CFRelease( newSafeArea );
				}
			}

		}
	}



	//special handle >>>
	if( (gWidthLogical != gVideoWidth || gHeightLogical != gVideoHeight) && (gWidthLogical!=0 && gHeightLogical !=0 ))
	{
		app_ulog( kLogLevelWarning, "The accessory special handle display \n");
		if(gAppStubDelegate.requestAccessoryUsbVidPid_f != NULL){
			app_ulog( kLogLevelWarning, "The accessory special handle display enter\n");
			uint16_t vid = 0x0000;
			uint16_t pid = 0x0000;
			int len = 2;
			gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
			//vid:0x0424 pid:0x4920 奇瑞主机
			app_ulog( kLogLevelWarning, "The accessory special handle vid:0x%04x pid:0x%04x \n", vid, pid);
			char	manufacturer [ 100 ];
			// __system_property_get("sys.shark.oem.car.manufacturer", manufacturer);
			if((vid == 0x0424 && pid == 0x4920) && strcmp("CHERY",manufacturer) == 0 )
			{
				gVideoWidth  = gWidthLogical;
				gVideoHeight = gHeightLogical;

				w = gVideoWidth;
				h = gVideoHeight;
				app_ulog( kLogLevelWarning, "The accessory special handle CHERY gVideoWidth:%d gVideoHeight:%d \n", gVideoWidth, gVideoHeight);
			}
		}
	}
	//special handle <<<

	char startpixels[100];
	char viewareas[100];
	int n = 0, len =0 ;
	n = sprintf( &startpixels[ len ], "%d",x );
	len += ( n );

	n = sprintf( &startpixels[ len ], "x" );
	len += ( n );

	n = sprintf( &startpixels[ len ], "%d", y );
	len += ( n );

	//viewareas
	n = 0;
	len = 0;
	n = sprintf( &viewareas[ len ], "%d",w );
	len += ( n );

	n = sprintf( &viewareas[ len ], "x" );
	len += ( n );

	n = sprintf( &viewareas[ len ], "%d", h );
	len += ( n );

	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int length = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&length);

	//新路虎不能设置裁剪The accessory vid:0x1004 pid:0x7722
//	if( (vid == 0x1004 && pid == 0x7722))
//	{
//	    __system_property_set("persist.zj.zsender.startpixels", "");
//	    __system_property_set("persist.zj.zsender.viewareas", "");
//	    __system_property_set("persist.lynx.display.startpixels", "");
//	    __system_property_set("persist.lynx.display.viewareas", "");
//
//	}else {
//	    __system_property_set("persist.zj.zsender.startpixels", startpixels);
//	    __system_property_set("persist.zj.zsender.viewareas", viewareas);
//	}

    // __system_property_set("persist.zj.zsender.startpixels", startpixels);
    // __system_property_set("persist.zj.zsender.viewareas", viewareas);

    app_ulog( kLogLevelWarning, "_AirPlayHandleSessionDisplay set startpixels = %s  \n", startpixels );
    app_ulog( kLogLevelWarning, "_AirPlayHandleSessionDisplay set viewarea = %s  \n", viewareas );

	char wphysical [100];
	snprintf( wphysical, 100, "%d", gWidthPhysical );
	//  __system_property_set("persist.panel.widthPhysical", wphysical);

	char hphysical [100];
	snprintf( hphysical, 100, "%d", gHeightPhysical );
	//  __system_property_set("persist.panel.heightPhysical", hphysical);
	//计算density
	if(gVideoWidth > 0 && gVideoHeight >0 && gWidthPhysical > 0 && gHeightPhysical >0)
	{
		uint64_t diagonal = sqrt((gVideoWidth*gVideoWidth) + (gVideoHeight*gVideoHeight));
		int wp = gWidthPhysical / 10; //mm -->cm
		int hp = gHeightPhysical / 10;//mm -->cm
		uint64_t diagonalInch = (sqrt((wp*wp) + (hp*hp)))/2.54;
		int densityDpi = (int)( diagonal / diagonalInch);
		if(densityDpi <= 160 ){
			densityDpi = 160;
		}else if(densityDpi <= 240 ){
			densityDpi = 240;
		}else if(densityDpi <= 1000 ){
			densityDpi = 320;
		}
		app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay densityDpi:%d \n", densityDpi );
		char density [100];
		snprintf( density, 100, "%d", densityDpi );
		//  __system_property_set("persist.xq.display.density", density);
	}

	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay gVideoWidth:%d \n", gVideoWidth );
	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay gVideoHeight:%d \n", gVideoHeight );
	app_ulog( kLogLevelWarning, "### _AirPlayHandleSessionDisplay gMaxFPS:%d \n", gMaxFPS );


	//vid:0x0424 pid:0x4920 奇瑞主机
//	if(vid == 0x0424 && pid == 0x4920)
//	{
//		//特殊主机，不处理
//	}
	if(gAppStubDelegate.airPlayScreenDisplay_f != NULL){
		gAppStubDelegate.airPlayScreenDisplay_f(0,gVideoWidth,gVideoHeight,gMaxFPS,w,h,gOriginalX,gOriginalY,gTouchFinger);
	}
	return( err );
}

//===========================================================================================================================
//	_AirPlayHandleSessionInfo
//===========================================================================================================================

static OSStatus
	_AirPlayHandleSessionInfo(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		inParams,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) inParams;
	(void) inContext;

	Boolean	rightHandDrive,oemIconVisible,nightMode;
	char	peerIdentifier[ 200 ];
	CFDictionaryGetCString(inParams,  CFSTR(kAirPlayKey_PublicHKID),peerIdentifier, sizeof(peerIdentifier), &err);
	app_ulog( kLogLevelWarning, "peerIdentifier %s \n", peerIdentifier );

	char	peerOemIcon[ 200 ];
	CFDictionaryGetCString(inParams,  CFSTR(kAirPlayProperty_OEMIcon),peerOemIcon, sizeof(peerOemIcon), &err);
	app_ulog( kLogLevelWarning, "peerOemIcon %s \n", peerOemIcon );

	rightHandDrive =  CFDictionaryGetBoolean( inParams, CFSTR( kAirPlayKey_RightHandDrive ), &err );
	app_ulog( kLogLevelWarning, "rightHandDrive %d \n", rightHandDrive );

	oemIconVisible =  CFDictionaryGetBoolean( inParams, CFSTR( kAirPlayProperty_OEMIconVisible ), &err );
	app_ulog( kLogLevelWarning, "oemIconVisible %d \n", oemIconVisible );

	nightMode =  CFDictionaryGetBoolean( inParams, CFSTR( kAirPlayKey_NightMode ), &err );
	app_ulog( kLogLevelWarning, "nightMode %d \n", nightMode );

	char	deviceID[ 200 ];
	CFDictionaryGetCString(inParams,  CFSTR(kAirPlayProperty_DeviceID),deviceID, sizeof(deviceID), &err);
	app_ulog( kLogLevelTrace, "deviceID %s \n", deviceID );



	if(gAppStubDelegate.airPlayInfoChanged_f!=NULL){
		gAppStubDelegate.airPlayInfoChanged_f(0,rightHandDrive,oemIconVisible,nightMode);
	}



	return( err );
}


static void aprintBuffer(char buf[], int length,char * tag) {
//*
	char *p = (char*) malloc(length * sizeof(char)*10);
	int offset = 0;
	memset(p, 0, length * 10);
	for (int i = 0; i <length; i++) {
		offset += sprintf(p + offset, " %02x", (unsigned char) buf[i]);
	}
	app_ulog( kLogLevelWarning, "%s ###buf = %s \n", tag, p);
	free(p);

}

static void _HandleTouchScreenHidReport(uint8_t * hidDesc,uint64_t  hidDescriptorLen){
	int logicalWidth  = 0;
	int logicalHeight = 0;
	gTouchFinger = 0;
	for(uint64_t i = 0; i< hidDescriptorLen;){
//		app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport =============\n" );
//		app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head = %02x  \n", hidDesc[i]);
		uint8_t bSize = hidDesc[i] & 0x03; //bytes size of data // bit[0] bit[1]
		uint8_t bType = ( hidDesc[i] & 0x0C ) >> 2; //0=Main 1=Global 2=Local  3=Reserved bit[2] bit[3]
		uint8_t bTag  = ( hidDesc[i] & 0xF0 ) >> 4; //the function of the item. bit[4]~bit[7]
//		app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport  bSize: %d \n" ,bSize);
//		app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport  bType: %d \n" ,bType);
//		app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport  bTag: %d \n" ,bTag);

		if(bTag == 2 && bType == 1){
			if(bSize == 2)
			{
				uint8_t tmp[2];
				tmp[0] = hidDesc[(i+1)];
				tmp[1] = hidDesc[(i+2)];
//				app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head = %02x  \n", hidDesc[i]);
//				app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head-value = %02x %02x  \n", tmp[0],tmp[1]);
				if( logicalWidth == 0)
				{
					logicalWidth  = ReadLittle16(tmp);
					logicalHeight = ReadLittle16(tmp);
					app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport  logicalWidth = %d \n", logicalWidth );
				}else{
					logicalHeight = ReadLittle16(tmp);
					app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport  logicalHeight = %d \n", logicalHeight );
				}
			}
		}
		else if(bTag == 0 && bType == 2){
			if(bSize == 1)
			{
				uint8_t tmp = hidDesc[(i+1)];
				if( tmp == 0x22 )
				{
					gTouchFinger ++ ;
				}
//				app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head = %02x  \n", hidDesc[i]);
//				app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head-value = %02x %02x  \n", tmp[0],tmp[1]);

			}
		}
		i += (bSize+1);
	}
	app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport  logicalWidth: %d logicalHeight: %d touchFinger: %d \n" ,logicalWidth, logicalHeight ,gTouchFinger);
	gWidthLogical = logicalWidth;
	gHeightLogical = logicalHeight;
	//special handle >>>
	if( (gWidthLogical != gVideoWidth || gHeightLogical != gVideoHeight) && (gWidthLogical!=0 && gHeightLogical !=0 ))
	{
		app_ulog( kLogLevelWarning, "The accessory special handle display \n");
		if(gAppStubDelegate.requestAccessoryUsbVidPid_f != NULL){
			app_ulog( kLogLevelWarning, "The accessory special handle display enter\n");
			uint16_t vid = 0x0000;
			uint16_t pid = 0x0000;
			int len = 2;
			gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
			//vid:0x0424 pid:0x4920 奇瑞主机
			app_ulog( kLogLevelWarning, "The accessory special handle vid:0x%04x pid:0x%04x \n", vid, pid);
//			if(vid == 0x0424 && pid == 0x4920)
//			{
//				gVideoWidth  = gWidthLogical;
//				gVideoHeight = gHeightLogical;
//			}
		}
	}
	//special handle <<<
}

static void _HandleTouchPadHidReport(uint8_t * hidDesc,uint64_t  hidDescriptorLen){
	int logicalWidth  = 0;
	int logicalHeight = 0;
	for(uint64_t i = 0; i< hidDescriptorLen;){
//		app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport =============\n" );
//		app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport head = %02x  \n", hidDesc[i]);
		uint8_t bSize = hidDesc[i] & 0x03; //bytes size of data
		uint8_t bType = ( hidDesc[i] & 0x0C ) >> 2; //0=Main 1=Global 2=Local  3=Reserved
		uint8_t bTag  = ( hidDesc[i] & 0xF0 ) >> 4; //the function of the item.
//		app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport  bSize: %d \n" ,bSize);
//		app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport  bType: %d \n" ,bType);
//		app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport  bTag: %d \n" ,bTag);

		if(bTag == 2 && bType == 1){
			if(bSize == 2)
			{
				uint8_t tmp[2];
				tmp[0] = hidDesc[(i+1)];
				tmp[1] = hidDesc[(i+2)];
//				app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head = %02x  \n", hidDesc[i]);
//				app_ulog( kLogLevelWarning, "_HandleTouchScreenHidReport head-value = %02x %02x  \n", tmp[0],tmp[1]);
				if( logicalWidth == 0)
				{
					logicalWidth  = ReadLittle16(tmp);
					app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport  logicalWidth = %d \n", logicalWidth );
				}else{
					logicalHeight = ReadLittle16(tmp);
					app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport  logicalHeight = %d \n", logicalHeight );
				}
			}
		}
		i += (bSize+1);
	}
	app_ulog( kLogLevelWarning, "_HandleTouchPadHidReport  logicalWidth: %d logicalHeight: %d \n" ,logicalWidth, logicalHeight );
	gTouchPadWidthLogical = logicalWidth;
	gTouchPadHeightLogical = logicalHeight;

	struct hid_user_register_data32 rdata;
	rdata.report_desc = (__u32)hidDesc;
	rdata.report_desc_len = hidDescriptorLen;
	rdata.user_id = touchPadUserId;
	ioctl(gHidDriverFd, HID_REGISTER, &rdata);
}


//===========================================================================================================================
const uint8_t					HIDTouch[4] = { 0x05, 0x0D, 0x09, 0x04};
const uint8_t					HIDTouchPad[4] = { 0x05, 0x0D, 0x09, 0x05};
const uint8_t					HIDKnob[4] = { 0x05, 0x01 ,0x09, 0x08};
const uint8_t					HIDSensor[4] = { 0x05, 0x20 ,0x09, 0x11};
const uint8_t					HIDTelephony[4] = { 0x05, 0x0B,0x09, 0x07 };
const uint8_t					HIDTelephony2[4] = { 0x05, 0x0B,0x09, 0x01 };
const uint8_t					HIDMediaButtons[4] = { 0x05, 0x0C,0x09, 0x01 };
const uint8_t					HIDMediaButtonsYaris[4] = { 0x05, 0x0C,0x09, 0x08 }; //Yaris Cross
//===========================================================================================================================
//	_AirPlayHandleSessionHIDReport
//===========================================================================================================================

static OSStatus
	_AirPlayHandleSessionHIDReport(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		dict,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) dict;
	(void) inContext;

	// HIDReport

	int64_t productID,hidCountryCode,hidVendorID;
	CFDataRef hidDescriptor;
	uint8_t * hidDescriptorPtr;
	uint64_t  hidDescriptorLen;
	uint8_t hidDescriptorUsage[4];
	CFStringRef displayUUID;
	CFStringRef uid;
	char * name[200];
	char * cstringUid[200];

	hidDescriptor = (CFDataRef) CFDictionaryGetValue( dict, CFSTR( kAirPlayKey_HIDDescriptor ) );
	//根据hid描述符匹配
	hidDescriptorPtr = CFDataGetBytePtr( hidDescriptor );
	hidDescriptorLen = CFDataGetLength( hidDescriptor );
//	aprintBuffer(hidDescriptorPtr, hidDescriptorLen, "HidReport: ");

	memset(hidDescriptorUsage,0,4);
	memcpy(hidDescriptorUsage,hidDescriptorPtr,4);

	productID = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_HIDProductID ),  &err );
	hidCountryCode = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_HIDCountryCode ),  &err );
	hidDescriptor = (CFDataRef) CFDictionaryGetValue( dict, CFSTR( kAirPlayKey_HIDDescriptor ) );
	displayUUID = CFDictionaryGetCFString( dict, CFSTR( kAirPlayKey_DisplayUUID ), &err );
	uid = CFDictionaryGetValue(dict, CFSTR(kAirPlayKey_UUID));
	hidVendorID = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_HIDVendorID ),  &err );
	CFDictionaryGetCString(dict,  CFSTR(kAirPlayKey_Name),name, sizeof(name), &err);
	CFDictionaryGetCString(dict,  CFSTR(kAirPlayKey_UUID),cstringUid, sizeof(cstringUid), &err);
	//uid = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%X"), inDeviceUID);

	if( gHidDriverFd < 0)
	{
		gHidDriverFd = open("/dev/sd_reverse_hid", O_RDONLY, 0777);
	}

	//Touch
	if(memcmp ( hidDescriptorUsage, HIDTouch, 4)==0){
		//这里需要获取width height
		aprintBuffer(hidDescriptorPtr, hidDescriptorLen, "HIDTouch");
		_HandleTouchScreenHidReport(hidDescriptorPtr, hidDescriptorLen);
		app_ulog( kLogLevelWarning, "###HIDTouch uid = %@ is %s \n",uid,name);
//		app_ulog( kLogLevelWarning, "###HIDTouch uid = %s is %s \n",cstringUid,name);

		if(strlen( gCStringTouchUID) > 0)
		{
			memset( gCStringTouchUID2, 0, 200);
			memcpy( gCStringTouchUID2, cstringUid, 200);
			app_ulog( kLogLevelWarning, "###gCStringTouchUID2 HIDTouch uid = %@ is %s \n",uid,name);
		}
		else {
			memset( gCStringTouchUID, 0, 200);
			memcpy( gCStringTouchUID, cstringUid, 200);
			app_ulog( kLogLevelWarning, "###gCStringTouchUID HIDTouch uid = %@ is %s \n",uid,name);
		}
	}

	//Knob
	else if(memcmp ( hidDescriptorUsage, HIDKnob, 4)==0)
	{
		struct hid_user_register_data32 rdata;
		rdata.report_desc = (__u32)hidDescriptorPtr;
		rdata.report_desc_len = hidDescriptorLen;
		rdata.user_id = knobButtonUserId;
		ioctl(gHidDriverFd, HID_REGISTER, &rdata);

		app_ulog( kLogLevelWarning, "###HIDKnob uid = %@ is %s \n",uid,name);
//		app_ulog( kLogLevelWarning, "###HIDKnob uid = %s is %s \n",cstringUid,name);
		memset( gCStringKnobUID, 0, 200);
		memcpy( gCStringKnobUID, cstringUid, 200);

	}

	//HIDSensor
	else if(memcmp ( hidDescriptorUsage, HIDSensor, 4)==0)
	{
		app_ulog( kLogLevelWarning, "###HIDSensor uid = %@ is %s \n",uid,name);
//		app_ulog( kLogLevelWarning, "###HIDSensor uid = %s is %s \n",cstringUid,name);
		memset( gCStringProxSensorUID, 0, 200);
		memcpy( gCStringProxSensorUID, cstringUid, 200);
	}

	//HIDTelephony
	else if((memcmp ( hidDescriptorUsage, HIDTelephony, 4)==0)
			|| memcmp ( hidDescriptorUsage, HIDTelephony2, 4)==0)
	{
		aprintBuffer(hidDescriptorPtr, hidDescriptorLen, "HIDTelephony");

		struct hid_user_register_data32 rdata;
		rdata.report_desc = (__u32)hidDescriptorPtr;
		rdata.report_desc_len = hidDescriptorLen;
		rdata.user_id = telephoneButtonUserId;
		ioctl(gHidDriverFd, HID_REGISTER, &rdata);

		app_ulog( kLogLevelWarning, "###HIDTelephony uid = %@ is %s \n",uid,name);
//		app_ulog( kLogLevelWarning, "###HIDTelephony uid = %s is %s \n",cstringUid,name);
		memset( gCStringTelephonyUID, 0, 200);
		memcpy( gCStringTelephonyUID, cstringUid, 200);
	}

	//HIDMediaButtons
	else if(memcmp ( hidDescriptorUsage, HIDMediaButtons, 4)==0 || memcmp ( hidDescriptorUsage, HIDMediaButtonsYaris, 4)==0)
	{
		if(memcmp ( hidDescriptorUsage, HIDMediaButtonsYaris, 4)==0)
		{
			HIDYarisCarModel = 1;
		}
		if(strlen(gCStringMediaButtonsUID) <=0 )
		{
			struct hid_user_register_data32 rdata;
			rdata.report_desc = (__u32)hidDescriptorPtr;
			rdata.report_desc_len = hidDescriptorLen;
			rdata.user_id = mediaButtonUserId;
			ioctl(gHidDriverFd, HID_REGISTER, &rdata);

			app_ulog( kLogLevelWarning, "###HIDMediaButtons uid = %@ is %s \n",uid,name);
//			app_ulog( kLogLevelWarning, "###HIDMediaButtons uid = %s is %s \n",cstringUid,name);
			aprintBuffer(hidDescriptorPtr, hidDescriptorLen, "MediaButtonHidReport: ");
			memset( gCStringMediaButtonsUID, 0, 200);
			memcpy( gCStringMediaButtonsUID, cstringUid, 200);
		}else{
			struct hid_user_register_data32 rdata;
			rdata.report_desc = (__u32)hidDescriptorPtr;
			rdata.report_desc_len = hidDescriptorLen;
			rdata.user_id = mediaButtonUserId2;
			ioctl(gHidDriverFd, HID_REGISTER, &rdata);

			app_ulog( kLogLevelWarning, "###HIDMediaButtons2 uid = %@ is %s \n",uid,name);
//			app_ulog( kLogLevelWarning, "###HIDMediaButtons2 uid = %s is %s \n",cstringUid,name);
			aprintBuffer(hidDescriptorPtr, hidDescriptorLen, "MediaButton2HidReport: ");
			memset( gCStringMediaButtonsUID1, 0, 200);
			memcpy( gCStringMediaButtonsUID1, cstringUid, 200);
		}


	}

	//HIDTouchPad
	else if(memcmp ( hidDescriptorUsage, HIDTouchPad, 4)==0)
	{
		app_ulog( kLogLevelWarning, "###HIDTouchPad uid = %@ is %s \n",uid,name);
//		app_ulog( kLogLevelWarning, "###HIDTouchPad uid = %s is %s \n",cstringUid,name);
		aprintBuffer(hidDescriptorPtr, hidDescriptorLen, "HIDTouchPadHidReport: ");
		if( strcasecmp("OFNInput",name ) == 0 )
		{
			app_ulog( kLogLevelWarning, "Optical Finger Navigation OFN ###HIDTouchPad uid = %@ is %s \n",uid,name);
			ofnInputSupport = 1;
		}

		if(strlen(gCStringTouchPadUID) == 0){
			_HandleTouchPadHidReport(hidDescriptorPtr, hidDescriptorLen);
			memset( gCStringTouchPadUID, 0, 200);
			memcpy( gCStringTouchPadUID, cstringUid, 200);
		}else{
			memset( gCStringTouchPadUID2, 0, 200);
			memcpy( gCStringTouchPadUID2, cstringUid, 200);
		}

	}
	return( err );
}


//===========================================================================================================================
//	_AirPlayHandleSessionAudioFormatReport
//===========================================================================================================================

static OSStatus
	_AirPlayHandleSessionAudioFormatReport(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		dict,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) dict;
	(void) inContext;

//	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport dict = %@ \n", dict );
	// AudioFormat
	if( dict )
	{
		int streamType = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_Type ) , &err);
		int64_t hu_audioFormat = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_AudioOutputFormats ) , &err);
		int64_t audioFormat = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_AudioOutputFormats ) , &err);
		int64_t hu_audioInputFormats = CFDictionaryGetInt64( dict, CFSTR( kAirPlayKey_AudioInputFormats ) , &err);
		CFStringRef inAudioType = CFDictionaryGetValue( dict, CFSTR( kAirPlayKey_AudioType ));
		CFStringRef audioOutputFormat = CFDictionaryGetValue( dict, CFSTR( kAirPlayKey_AudioOutputFormats ));
		CFStringRef audioInputFormat = CFDictionaryGetValue( dict, CFSTR( kAirPlayKey_AudioInputFormats ));
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport streamType = %d \n", streamType );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport audioFormat = %ld \n", audioFormat );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport hu_audioInputFormats = %ld \n", hu_audioInputFormats );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport inAudioType = %1@ \n", inAudioType );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport audioOutputFormat = %1@ \n", audioOutputFormat );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport audioInputFormat = %1@ \n", audioInputFormat );

		if( audioOutputFormat != NULL )
		{
			char formatcstring [ 200 ];
			memset(formatcstring, 0, 200);
			CFDictionaryGetCString(dict,  CFSTR(kAirPlayKey_AudioOutputFormats),formatcstring, 200, &err);

			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport Output cstringFormat = %s \n", formatcstring );

			int32_t SR = TextToInt32( formatcstring, 20, 10 );
			audioFormat = SR; //获取到采样率
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport Output SR = %ld \n", SR );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport Output audioFormat = %ld \n", audioFormat );
		}

		if( audioInputFormat != NULL )
		{
			char formatcstring [ 200 ];
			memset(formatcstring, 0, 200);
			CFDictionaryGetCString(dict,  CFSTR(kAirPlayKey_AudioInputFormats),formatcstring, 200, &err);

			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport Input cstringFormat = %s \n", formatcstring );

			int32_t SR = TextToInt32( formatcstring, 20, 10 );
			hu_audioInputFormats = SR; //获取到采样率
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport Input SR = %ld \n", SR );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport Input audioFormat = %ld \n", hu_audioInputFormats );
		}

		int sampleRate = 48000;
		int bits = 16;
		int channels = 1;
		int allInOne = 0;
		int audioLatency = 50 * 1000 ;

		if( inAudioType == NULL )
		{
//			inAudioType = CFSTR(kAirPlayAudioType_Compatibility);
			if(streamType == kAirPlayStreamType_MainAudio)
			{
				allInOne = 1;
			}
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport all in one streamType = %d \n", streamType );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport all in one audioFormat = %ld \n", audioFormat );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport all in one inAudioType = %1@ \n", inAudioType );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport all in one audioOutputFormat = %1@ \n", audioOutputFormat );
		}

		if ((CFStringCompare( inAudioType,  CFSTR(kAirPlayAudioType_Media), 0) == kCFCompareEqualTo) || allInOne)
		{
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport media audioFormat = %ld \n", audioFormat );
			sampleRate = 48000;
			bits = 16;
			channels = 2;
			audioLatency = mediaLatencyAudioLatencyMicros;

			bool priority44K = false;
			//vid pid
			uint16_t vid_usb = 0x0000;
			uint16_t pid_usb = 0x0000;
			int len_usb = 2;
			gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid_usb,&pid_usb,&len_usb);
			//vid:0x08e4 pid:0x01b2 丰田塞纳
			// vid:0x05c6 pid:0x9031
			if( (vid_usb == 0x08e4 && pid_usb == 0x01b2) ||(vid_usb == 0x05c6 && pid_usb == 0x9031) )
			{
				priority44K = true;
			}

			if( ((kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo  & audioFormat) != 0)  && ( priority44K || (gMediaAudioLatency == NULL ?  1: ( mediaLatencySampleRate == 44100 ) )  ))
			{
				sampleRate = 44100;
				bits = 16;
				channels = 2;
			}
			else if( ((kAirPlayAudioFormat_PCM_44KHz_16Bit_Mono  & audioFormat) != 0) &&( priority44K || (gMediaAudioLatency == NULL ?  1:   ( mediaLatencySampleRate == 44100 )) ))
			{
				sampleRate = 44100;
				bits = 16;
				channels = 1;
			}
			else if( ((kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo & audioFormat) != 0) && ( gMediaAudioLatency == NULL ?  1 : ( mediaLatencySampleRate == 48000 ) ))
			{
				sampleRate = 48000;
				bits = 16;
				channels = 2;
			}
			else if( ((kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono & audioFormat) != 0) && ( gMediaAudioLatency == NULL ?  1:( mediaLatencySampleRate == 48000 ) ))
			{
				sampleRate = 48000;
				bits = 16;
				channels = 1;
			}

			if( sampleRate == 44100 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_44KHz_16Bit_Mono;
			}
			else if( sampleRate == 48000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono;
			}
			mediaSampleRate = sampleRate;
			mediaBits = bits;
			mediaChannels = channels;
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport media sampleRate = %d \n", mediaSampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport media bits = %d \n", mediaBits );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport media channels = %d \n", mediaChannels );

			if( !gMediaAudioFormats )
			{
				gMediaAudioFormats = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			}
			CFDictionarySetInt64( gMediaAudioFormats, CFSTR( kAirPlayKey_Type ), streamType );
			CFDictionarySetInt64( gMediaAudioFormats, CFSTR( kAirPlayKey_AudioOutputFormats ), audioFormat );
			CFDictionarySetValue( gMediaAudioFormats, CFSTR( kAirPlayKey_AudioType ), inAudioType );
			CFDictionarySetInt64( gMediaAudioFormats, CFSTR( kAirPlayKey_AudioLatencyMs ), audioLatency / 1000 );
			if(gAppStubDelegate.airPlayAudioFormatInfo_f != NULL){
				gAppStubDelegate.airPlayAudioFormatInfo_f(0,AUDIO_CHANNEL_MEDIA,mediaSampleRate,mediaBits,mediaChannels);
			}
		}
		if (((CFStringCompare( inAudioType,  CFSTR(kAirPlayAudioType_Telephony), 0) == kCFCompareEqualTo) && streamType == kAirPlayStreamType_MainAudio
				&& audioInputFormat != NULL ) || allInOne)
		{
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport telephony hu_audioInputFormats = %ld \n", hu_audioInputFormats );
			sampleRate = 16000;
			bits = 16;
			channels = 1;
			audioLatency = phoneLatencyAudioLatencyMicros;
			if( inAudioType != NULL  || allInOne )
			{

				//vid pid
				uint16_t vid_usb = 0x0000;
				uint16_t pid_usb = 0x0000;
				int len_usb = 2;
				gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid_usb,&pid_usb,&len_usb);
				//0x08e4 pid:0x01b2
				// 0x108c pid:0x0178 标雪主机
				bool ignore24k_pcm = false;

				if(( vid_usb == 0x08e4 && pid_usb == 0x01b2) || (  vid_usb == 0x108c && pid_usb == 0x0178 ))
				{
					ignore24k_pcm = true;
				}
				app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport telephony ignore24k_pcm = %d .",ignore24k_pcm);

				if( ((kAirPlayAudioFormat_PCM_24KHz_16Bit_Stereo  & hu_audioInputFormats) != 0) && ( !ignore24k_pcm ) && (gTelephoneAudioLatency == NULL ?1:
						( phoneLatencySampleRate == 24000) ) )
				{
					sampleRate = 24000;
					bits = 16;
					channels = 2;
				}
				else if( ((kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono  & hu_audioInputFormats) != 0) && ( !ignore24k_pcm ) /* &&( gTelephoneAudioLatency == NULL ?1:
						( phoneLatencySampleRate == 24000) )*/ )
				{
					app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport telephony kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono selected.");
					sampleRate = 24000;
					bits = 16;
					channels = 1;
				}
				else if( ((kAirPlayAudioFormat_PCM_16KHz_16Bit_Stereo  & hu_audioInputFormats) != 0)  &&(gTelephoneAudioLatency == NULL ?1:
						( phoneLatencySampleRate == 16000 )  ))
				{
					app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport telephony kAirPlayAudioFormat_PCM_16KHz_16Bit_Stereo selected.");
					sampleRate = 16000;
					bits = 16;
					channels = 2;
				}
				else if( ((kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono  & hu_audioInputFormats) != 0)  &&(  gTelephoneAudioLatency == NULL ?1:
						( phoneLatencySampleRate == 16000) )  )
				{
					app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport telephony kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono selected.");
					sampleRate = 16000;
					bits = 16;
					channels = 1;
				}
				else if( ((kAirPlayAudioFormat_PCM_8KHz_16Bit_Stereo  & hu_audioInputFormats) != 0)  && ( gTelephoneAudioLatency == NULL ?1:
						( phoneLatencySampleRate == 8000) )  )
				{
					sampleRate = 8000;
					bits = 16;
					channels = 2;
				}
				else if( ((kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono  & hu_audioInputFormats) != 0)  &&
						( gTelephoneAudioLatency == NULL ?1:( phoneLatencySampleRate == 8000))   )
				{
					sampleRate = 8000;
					bits = 16;
					channels = 1;
				}

			}

			if( sampleRate == 8000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_8KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono;
			}
			else if( sampleRate == 16000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_16KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono;
			}
			else if( sampleRate == 24000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_24KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono;
			}

			phoneSampleRate = sampleRate;
			phoneBits = bits;
			phoneChannels = channels;
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport phone sampleRate = %d \n", phoneSampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport phone bits = %d \n", phoneBits );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport phone channels = %d \n", phoneChannels );
			if( !gTelephoneAudioFormats )
			{
				gTelephoneAudioFormats = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			}
			CFDictionarySetInt64( gTelephoneAudioFormats, CFSTR( kAirPlayKey_Type ), streamType );
			CFDictionarySetInt64( gTelephoneAudioFormats, CFSTR( kAirPlayKey_AudioOutputFormats ), audioFormat );
			CFDictionarySetValue( gTelephoneAudioFormats, CFSTR( kAirPlayKey_AudioType ), inAudioType );
			CFDictionarySetInt64( gTelephoneAudioFormats, CFSTR( kAirPlayKey_AudioLatencyMs ), audioLatency / 1000 );
			if(gAppStubDelegate.airPlayAudioFormatInfo_f != NULL){
				gAppStubDelegate.airPlayAudioFormatInfo_f(0,AUDIO_CHANNEL_PHONE,phoneSampleRate,phoneBits,phoneChannels);
			}
		}
		if (((CFStringCompare( inAudioType,  CFSTR(kAirPlayAudioType_SpeechRecognition), 0) == kCFCompareEqualTo) && streamType == kAirPlayStreamType_MainAudio
				&& audioInputFormat != NULL ) || allInOne)
		{
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport speech hu_audioInputFormats = %ld \n", hu_audioInputFormats );
			sampleRate = 16000;
			bits = 16;
			audioLatency = speechLatencyAudioLatencyMicros;
			channels = 1;
			if( inAudioType != NULL || allInOne)
			{

				if( ((kAirPlayAudioFormat_PCM_16KHz_16Bit_Stereo  & hu_audioInputFormats) != 0)  && ( gSpeechAudioLatency == NULL ?1:
						( speechLatencySampleRate == 16000) )    )
				{
					sampleRate = 16000;
					bits = 16;
					channels = 2;
				}
				else if( ((kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono  & hu_audioInputFormats) != 0)  && ( gSpeechAudioLatency == NULL ?1:
						(speechLatencySampleRate == 16000) )     )
				{
					sampleRate = 16000;
					bits = 16;
					channels = 1;
				}
				else if( ((kAirPlayAudioFormat_PCM_24KHz_16Bit_Stereo  & hu_audioInputFormats) != 0)  && ( gSpeechAudioLatency == NULL ?1:
						(speechLatencySampleRate == 24000) )   )
				{
					sampleRate = 24000;
					bits = 16;
					channels = 2;
				}
				else if( ((kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono  & hu_audioInputFormats) != 0)  &&
						(gSpeechAudioLatency == NULL ?1:(speechLatencySampleRate == 24000) )    )
				{
					sampleRate = 24000;
					bits = 16;
					channels = 1;
				}
				else if( ((kAirPlayAudioFormat_PCM_8KHz_16Bit_Stereo  & hu_audioInputFormats) != 0)  &&
						( gSpeechAudioLatency == NULL ?1:(speechLatencySampleRate == 8000) )      )
				{
					sampleRate = 8000;
					bits = 16;
					channels = 2;
				}
				else if( ((kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono  & hu_audioInputFormats) != 0)  &&
						( gSpeechAudioLatency == NULL ?1:( speechLatencySampleRate == 8000) )       )
				{
					sampleRate = 8000;
					bits = 16;
					channels = 1;
				}
			}

			if( sampleRate == 8000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_8KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono;
			}
			else if( sampleRate == 16000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_16KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono;
			}
			else if( sampleRate == 24000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_24KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono;
			}

			speechSampleRate = sampleRate;
			speechBits = bits;
			speechChannels = channels;
			audioLatency = alertLatencyAudioLatencyMicros;
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport speech sampleRate = %d \n", speechSampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport speech bits = %d \n", speechBits );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport speech channels = %d \n", speechChannels );
			if( !gSpeechAudioFormats )
			{
				gSpeechAudioFormats = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			}
			CFDictionarySetInt64( gSpeechAudioFormats, CFSTR( kAirPlayKey_Type ), streamType );
			CFDictionarySetInt64( gSpeechAudioFormats, CFSTR( kAirPlayKey_AudioOutputFormats ), audioFormat );
			CFDictionarySetValue( gSpeechAudioFormats, CFSTR( kAirPlayKey_AudioType ), inAudioType );
			CFDictionarySetInt64( gSpeechAudioFormats, CFSTR( kAirPlayKey_AudioLatencyMs ), audioLatency / 1000 );
			if(gAppStubDelegate.airPlayAudioFormatInfo_f != NULL){
				gAppStubDelegate.airPlayAudioFormatInfo_f(0,AUDIO_CHANNEL_SPEECH,speechSampleRate,speechBits,speechChannels);
			}
		}
		if ((CFStringCompare( inAudioType,  CFSTR(kAirPlayAudioType_Default), 0) == kCFCompareEqualTo) || allInOne || streamType == kAirPlayStreamType_AltAudio )
		{
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport alert audioFormat = %ld \n", audioFormat );
			sampleRate = 48000;
			bits = 16;
			channels = 2;
			if( ((kAirPlayAudioFormat_PCM_16KHz_16Bit_Stereo  & audioFormat) != 0)  && ( gAltAudioLatency == NULL ?1:
					( alertLatencySampleRate == 16000) )        )
			{
				sampleRate = 16000;
				bits = 16;
				channels = 2;
			}
			else if( ((kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono  & audioFormat) != 0)  && ( gAltAudioLatency == NULL ?1:
					(alertLatencySampleRate == 16000 ) )         )
			{
				sampleRate = 16000;
				bits = 16;
				channels = 1;
			}
			else if( ((kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo  & audioFormat) != 0)  && ( gAltAudioLatency == NULL ?1:
					( alertLatencySampleRate == 44100) )        )
			{
				sampleRate = 44100;
				bits = 16;
				channels = 2;
			}
			else if( ((kAirPlayAudioFormat_PCM_44KHz_16Bit_Mono  & audioFormat) != 0)  && ( gAltAudioLatency == NULL ?1:
					(alertLatencySampleRate == 44100 ) )         )
			{
				sampleRate = 44100;
				bits = 16;
				channels = 1;
			}
			else if( ((kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo  & audioFormat) != 0)  && ( gAltAudioLatency == NULL ?1:
					( alertLatencySampleRate == 48000 ))   )
			{
				sampleRate = 48000;
				bits = 16;
				channels = 2;
			}
			else if( ((kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono  & audioFormat) != 0) && ( gAltAudioLatency == NULL ?1:
					( alertLatencySampleRate == 48000))    )
			{
				sampleRate = 48000;
				bits = 16;
				channels = 1;
			}

			if( sampleRate == 44100 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_44KHz_16Bit_Mono;
			}
			else if( sampleRate == 48000 )
			{
				audioFormat = channels == 2 ? kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo : kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono;
			}
			alertSampleRate = sampleRate;
			alertBits = bits;
			alertChannels = channels;
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport alert sampleRate = %d \n", alertSampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport alert bits = %d \n", alertBits );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioFormatReport alert channels = %d \n", alertChannels );
			if( !gAltAudioFormats )
			{
				gAltAudioFormats = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			}
			CFDictionarySetInt64( gAltAudioFormats, CFSTR( kAirPlayKey_Type ), streamType );
			CFDictionarySetInt64( gAltAudioFormats, CFSTR( kAirPlayKey_AudioOutputFormats ), audioFormat );
			CFDictionarySetValue( gAltAudioFormats, CFSTR( kAirPlayKey_AudioType ), inAudioType );
			CFDictionarySetInt64( gAltAudioFormats, CFSTR( kAirPlayKey_AudioLatencyMs ), audioLatency / 1000 );
			if(gAppStubDelegate.airPlayAudioFormatInfo_f != NULL){
				gAppStubDelegate.airPlayAudioFormatInfo_f(0,AUDIO_CHANNEL_NAVIGATION,sampleRate,bits,channels);
			}
		}
	}
	return( err );
}

//===========================================================================================================================
//	_AirPlayHandleSessionAudioLatencyReport
//===========================================================================================================================

static OSStatus
	_AirPlayHandleSessionAudioLatencyReport(
		AirPlayReceiverSessionRef 	inSession,
        CFDictionaryRef     		dict,
		void *						inContext )
{
	OSStatus err;

	(void) inSession;
    (void) dict;
	(void) inContext;

//	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport dict = %@ \n", dict );
	// AudioFormat
	if( dict )
	{
		int64_t hu_SampleRate = CFDictionaryGetInt64( dict, kAudioSessionKey_SampleRate , &err);
		int64_t hu_SampleSize = CFDictionaryGetInt64( dict, kAudioSessionKey_SampleSize , &err);
		int64_t hu_SampleChannel = CFDictionaryGetInt64( dict, kAudioSessionKey_Channels , &err);
		int64_t hu_InputLatencyMicros = CFDictionaryGetInt64( dict, kAudioSessionKey_InputLatencyMicros , &err);
		int64_t hu_OutputLatencyMicros = CFDictionaryGetInt64( dict, kAudioSessionKey_OutputLatencyMicros , &err);
		int64_t hu_streamType = CFDictionaryGetInt64( dict, kAudioSessionKey_Type , &err);
		CFStringRef hu_audioType = CFDictionaryGetValue( dict, kAudioSessionKey_AudioType );

		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport hu_SampleRate = %d \n", hu_SampleRate );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport hu_SampleSize = %ld \n", hu_SampleSize );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport hu_SampleChannel = %ld \n", hu_SampleChannel );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport hu_InputLatencyMicros = %ld \n", hu_InputLatencyMicros );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport hu_OutputLatencyMicros = %ld \n", hu_OutputLatencyMicros );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport hu_streamType = %d \n", hu_streamType );
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport inAudioType = %1@ \n", hu_audioType );

		int allInOne = 0;
		if( hu_audioType == NULL )
		{
			if(hu_streamType == kAirPlayStreamType_MainAudio)
			{
				allInOne = 1;
			}
			
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one hu_SampleRate = %d \n", hu_SampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one hu_SampleSize = %ld \n", hu_SampleSize );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one hu_SampleChannel = %ld \n", hu_SampleChannel );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one hu_InputLatencyMicros = %ld \n", hu_InputLatencyMicros );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one hu_OutputLatencyMicros = %ld \n", hu_OutputLatencyMicros );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one hu_streamType = %d \n", hu_streamType );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport all in one inAudioType = %1@ \n", hu_audioType );

		}

		if ((CFStringCompare( hu_audioType,  CFSTR(kAirPlayAudioType_Media), 0) == kCFCompareEqualTo) || allInOne)
		{
			if( hu_SampleRate != 0 )
			{
				mediaLatencySampleRate = hu_SampleRate;
			}
			mediaLatencyAudioLatencyMicros = hu_OutputLatencyMicros;
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport media sampleRate = %d \n", mediaLatencySampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport media mediaLatencyAudioLatencyMicros = %d \n", mediaLatencyAudioLatencyMicros );

			if( !gMediaAudioLatency)
			{
				gMediaAudioLatency = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			}
			CFDictionarySetInt64( gMediaAudioLatency, kAudioSessionKey_Type, hu_streamType );
			CFDictionarySetInt64( gMediaAudioLatency,kAudioSessionKey_SampleRate, mediaLatencySampleRate );
			CFDictionarySetInt64( gMediaAudioLatency, kAudioStreamProperty_Latency, mediaLatencyAudioLatencyMicros );
			CFDictionarySetValue( gMediaAudioLatency, kAudioSessionKey_AudioType, hu_audioType );
		}
		if (((CFStringCompare( hu_audioType,  CFSTR(kAirPlayAudioType_Telephony), 0) == kCFCompareEqualTo) && hu_streamType == kAirPlayStreamType_MainAudio )
				|| allInOne)
		{
			if( hu_SampleRate == 8000 || hu_SampleRate == 16000 || hu_SampleRate == 24000 )
			{
				phoneLatencySampleRate = hu_SampleRate;
				phoneLatencyAudioLatencyMicros = hu_InputLatencyMicros;
				app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport phone sampleRate = %d \n", phoneLatencySampleRate );
				app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport phone phoneLatencyAudioLatencyMicros = %ld \n", phoneLatencyAudioLatencyMicros );

				if( !gTelephoneAudioLatency)
				{
					gTelephoneAudioLatency = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
				}
				CFDictionarySetInt64( gTelephoneAudioLatency, kAudioSessionKey_Type, hu_streamType );
				CFDictionarySetInt64( gTelephoneAudioLatency, kAudioSessionKey_SampleRate, phoneLatencySampleRate );
				CFDictionarySetInt64( gTelephoneAudioLatency, kAudioStreamProperty_Latency, phoneLatencyAudioLatencyMicros );
				CFDictionarySetValue( gTelephoneAudioLatency, kAudioSessionKey_AudioType, hu_audioType );

			}
		}
		if (((CFStringCompare( hu_audioType,  CFSTR(kAirPlayAudioType_SpeechRecognition), 0) == kCFCompareEqualTo) && hu_streamType == kAirPlayStreamType_MainAudio )
				|| allInOne)
		{
			if( hu_SampleRate == 8000 || hu_SampleRate == 16000 || hu_SampleRate == 24000 )
			{
				speechLatencySampleRate = hu_SampleRate;
				speechLatencyAudioLatencyMicros = hu_InputLatencyMicros;
				app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport speech sampleRate = %d \n", speechLatencySampleRate );
				app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport speech speechLatencyAudioLatencyMicros = %ld \n", speechLatencyAudioLatencyMicros );

				if( !gSpeechAudioLatency)
				{
					gSpeechAudioLatency = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
				}
				CFDictionarySetInt64( gSpeechAudioLatency, kAudioSessionKey_Type, hu_streamType );
				CFDictionarySetInt64( gSpeechAudioLatency, kAudioSessionKey_SampleRate, speechLatencySampleRate );
				CFDictionarySetInt64( gSpeechAudioLatency, kAudioStreamProperty_Latency, speechLatencyAudioLatencyMicros );
				CFDictionarySetValue( gSpeechAudioLatency, kAudioSessionKey_AudioType, hu_audioType );

			}
		}
		if ((CFStringCompare( hu_audioType,  CFSTR(kAirPlayAudioType_Default), 0) == kCFCompareEqualTo) || allInOne)
		{
			if( hu_SampleRate != 0 )
			{
				alertLatencySampleRate = hu_SampleRate;
			}

			alertLatencyAudioLatencyMicros = hu_OutputLatencyMicros;
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport alert sampleRate = %d \n", alertLatencySampleRate );
			app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioLatencyReport alert alertLatencyAudioLatencyMicros = %ld \n", alertLatencyAudioLatencyMicros );

			if( !gAltAudioLatency)
			{
				gAltAudioLatency = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
			}
			CFDictionarySetInt64( gAltAudioLatency, kAudioSessionKey_Type, hu_streamType );
			CFDictionarySetInt64( gAltAudioLatency, kAudioSessionKey_SampleRate, alertLatencySampleRate );
			CFDictionarySetInt64( gAltAudioLatency, kAudioStreamProperty_Latency, alertLatencyAudioLatencyMicros );
			CFDictionarySetValue( gAltAudioLatency, kAudioSessionKey_AudioType, hu_audioType );
		}
	}
	return( err );
}

static void
	_AirPlayHandleSetEnhancedSiriParams(
		AirPlayReceiverSessionRef	inSession,
		AirPlayVoiceActivationMode	inMode,
		CFStringRef					inLanguage,
		void *						inContext )
{
	(void) inMode;
	(void) inLanguage;
	(void) inContext;

	// Start the AuxIn buffering for the plugin
	AirPlayReceiverSessionAuxInStart( inSession, NULL );
}

static void
	_AirPlayHandleSuggestUI(
		AirPlayReceiverSessionRef 	inSession,
		CFArrayRef			inURLs,
		void *				inContext )
{
	CFRange range = CFRangeMake( 0, CFArrayGetCount( inURLs ) );

	if( CFArrayContainsValue( inURLs, range, CFSTR( kAirPlayKey_ScreenURLsInstrumentCluster ) ) )
	{
		CFStringRef url;
		char compassQuery[32];
		char etaQuery[32];
		char speedLimitQuery[32];

		snprintf( compassQuery, sizeof( compassQuery ), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowCompass, "user" );
		snprintf( etaQuery, sizeof( etaQuery ), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowETA, "user" );
		snprintf( speedLimitQuery, sizeof( speedLimitQuery ), "%s=%s", kAirPlayKey_ScreenURLsInstrumentClusterOptionShowCompass, "user" );

		url = CFStringCreateWithFormat( kCFAllocatorDefault, NULL, CFSTR( "%s?%s&%s&%s"), kAirPlayKey_ScreenURLsInstrumentCluster, compassQuery, etaQuery, speedLimitQuery );
		if( url )
		{
			AirPlayReceiverSessionShowUI( inSession, kAltScreenUUID, url, NULL, NULL );
			CFRelease( url );
		}
	}
	else
	{
		AirPlayReceiverSessionStopUI( inSession, kAltScreenUUID, NULL, NULL );
	}
}

static void
    _AirPlayHandleDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    double						inVolume,
	    void *						inContext )
{
	(void) inContext;
    (void) inSession;

	app_ulog( kLogLevelNotice, "AirPlayHandleDuckAudio DurationSecs: %f, Volume: %f.\n", inDurationSecs, inVolume );

//	handleSessionDuckAudio( inDurationSecs, inVolume );
}

static void
    _AirPlayHandleunDuckAudio(
	    AirPlayReceiverSessionRef	inSession,
	    double						inDurationSecs,
	    void *						inContext )
{
	(void) inContext;
    (void) inSession;

	app_ulog( kLogLevelNotice, "AirPlayHandleunDuckAudio DurationSecs: %f.\n", inDurationSecs );

//	handleSessionUnduckAudio( inDurationSecs );
}
static void
    _AirPlayHandleSessionScreenStart()
{

	gInitialModeChangesRaw.screen.type = kAirPlayTransferType_Untake;
	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionScreenStart \n" );

	if(gAppStubDelegate.requestScreenRecordStart_f!=NULL){
		gAppStubDelegate.requestScreenRecordStart_f(0,gVideoWidth,gVideoHeight,gMaxFPS);
	}
}

static void
    _AirPlayHandleSessionScreenStop()
{

	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionScreenStop \n" );
	if(gAppStubDelegate.requestScreenRecordStop_f!=NULL){
		gAppStubDelegate.requestScreenRecordStop_f( 0 );
	}

}


static void
    _AirPlayHandleSessionForceKeyFrame()
{

	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionForceKeyFrame \n" );
	if( videoKeyFrame )
	{
		app_ulog( kLogLevelNotice, "_AirPlayHandleSessionForceKeyFrame send \n" );
		ScreenStreamInputProcessData(0, videoKeyFrame, videoKeyFrameLen, 1, 0, NULL);
	}

}


static void
    _AirPlayHandleSessionSetNightMode(uint8_t mode)
{

	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionSetNightMode nightMode = %d \n" ,mode);
	if( gAppStubDelegate.airPlaySetNightMode_f != NULL ){
		gAppStubDelegate.airPlaySetNightMode_f( 0, mode );
	}

}

static void
	_AirPlayHandleSessionUpdateViewAreaIndex(uint8_t index){
	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionUpdateViewAreaIndex index = %d \n" ,index);

}

static void
    _AirPlayHandleSessionAudioStreamStart(
    		const char* audioType,
            int handle,
            CarplayAudioStreamType streamType,
            int rate,
            int bits,
            int channels,
            void* inStream)
{

	int appAudioChannel = AUDIO_CHANNEL_MEDIA;
	switch(streamType){
	   case AudioStreamMedia:
		   appAudioChannel = AUDIO_CHANNEL_MEDIA;
		   break;
	   case AudioStreamTelephony:
		   appAudioChannel = AUDIO_CHANNEL_PHONE;
		   break;
	   case AudioStreamRecognition:
		   appAudioChannel = AUDIO_CHANNEL_SPEECH;
		   break;
	   case AudioStreamAlert:
		   appAudioChannel = AUDIO_CHANNEL_NAVIGATION;
		   break;
	}
	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioStreamStart audioType = % s\n",audioType );
	if( gAppStubDelegate.requestAudioRecordStart_f != NULL ){
		gAppStubDelegate.requestAudioRecordStart_f(0,appAudioChannel);
		if(streamType == AudioStreamTelephony || streamType == AudioStreamRecognition){
			gAppStubDelegate.requestMicrophoneStart_f(0,appAudioChannel);
		}

	}

}


static void
    _AirPlayHandleSessionAudioStreamStop(int handle, CarplayAudioStreamType streamType)
{

	app_ulog( kLogLevelNotice, "_AirPlayHandleSessionAudioStreamStop \n" );
	int appAudioChannel = AUDIO_CHANNEL_MEDIA;
	switch(streamType){
	   case AudioStreamMedia:
		   appAudioChannel = AUDIO_CHANNEL_MEDIA;
		   break;
	   case AudioStreamTelephony:
		   appAudioChannel = AUDIO_CHANNEL_PHONE;
		   break;
	   case AudioStreamRecognition:
		   appAudioChannel = AUDIO_CHANNEL_SPEECH;
		   break;
	   case AudioStreamAlert:
		   appAudioChannel = AUDIO_CHANNEL_NAVIGATION;
		   break;
	}
	if( gAppStubDelegate.requestAudioRecordStop_f != NULL ){
		gAppStubDelegate.requestAudioRecordStop_f(0,appAudioChannel);
		if(streamType == AudioStreamTelephony || streamType == AudioStreamRecognition){
			gAppStubDelegate.requestMicrophoneStop_f(0,appAudioChannel);
		}

	}

}

OSStatus _setupHIDDevices( void )
{
	// Assign a unique identifier for each HID device
	if( gHiFiTouch || gLoFiTouch )
	{
		gTouchUID = gNextDeviceUID++;
	}

	if( gKnob )
	{
		gKnobUID = gNextDeviceUID++;
	}

	if( gProxSensor )
	{
		gProxSensorUID = gNextDeviceUID++;
	}

	if( gTelephony )
	{
        gTelephonyUID = gNextDeviceUID++;
    }

	if ( gMediaButtons )
	{
		gMediaButtonsUID = gNextDeviceUID++;
	}

	return( kNoErr );
}

//===========================================================================================================================
//	_touchScreenUpdate
//===========================================================================================================================

static OSStatus _touchScreenUpdate( uint8_t inPress, uint16_t inX, uint16_t inY )
{
	OSStatus		err;
	uint8_t			report[ 5 ];

	app_ulog( kLogLevelNotice, "TouchScreenUpdate Press: %d ( %d, %d )\n", inPress, inX, inY );

	HIDTouchScreenFillReport( report, inPress, inX, inY );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gTouchUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	_telephonyUpdate
//===========================================================================================================================

static OSStatus _telephonyUpdate( int inButtonIndex )
{
	app_ulog( kLogLevelNotice, "HIDTelephony buttonIndex: %d\n", inButtonIndex );
	OSStatus		err;
	uint8_t			report[ 1 ];

	HIDTelephonyFillReport( report, inButtonIndex );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gTelephonyUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	_mediaButtonsUpdate
//===========================================================================================================================

static OSStatus _mediaButtonsUpdate( int inButtonIndex )
{
	app_ulog( kLogLevelNotice, "mediaButtonsUpdate buttonIndex %d\n", inButtonIndex );
	OSStatus		err;
	uint8_t			report[ 1 ];

	HIDMediaButtonsFillReport( report, inButtonIndex );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gMediaButtonsUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	KnobUpdate
//===========================================================================================================================

static double TranslateValue( double inValue, double inOldMin, double inOldMax, double inNewMin, double inNewMax )
{
	return( ( ( ( inValue - inOldMin ) / ( inOldMax - inOldMin ) ) * ( inNewMax - inNewMin ) ) + inNewMin );
}

OSStatus	KnobUpdate( void )
{
	OSStatus		err;
	uint8_t			report[ 4 ];
	int8_t			x;
	int8_t			y;

	// Normalize X and Y values to integers between -127 and 127
	x = (int8_t) TranslateValue( gXPosition, kMinXPosition, kMaxXPosition, -127, 127 );
	y = (int8_t) TranslateValue( gYPosition, kMinYPosition, kMinYPosition, -127, 127 );

	// A HIDKnobFillReport must be sent on both button pressed and button released events
	HIDKnobFillReport( report, gSelectButtonPressed, gHomeButtonPressed, gBackButtonPressed, x, y, gWheelPositionRelative );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gKnobUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	_proxSensorUpdate
//===========================================================================================================================

static OSStatus _proxSensorUpdate( bool inProxSensorPresence )
{
	OSStatus		err;
	uint8_t			report[ 1 ];

	HIDProximityFillReport( report, inProxSensorPresence );

	AirPlayReceiverSessionSendHIDReport( gAirPlaySession, gProxSensorUID, report, sizeof( report ) );
	require_noerr( err, exit );

exit:
	return( err );
}

//===========================================================================================================================
//	_ParseModes
//===========================================================================================================================

static OSStatus	_ParseModes( const char *inArg )
{
	OSStatus			err;
	const char *		token;
	size_t				len;

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != '=' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( ( strnicmpx( token, len, kAirPlayResourceIDString_MainScreen ) == 0 ) ||
		( strnicmpx( token, len, "screen" ) == 0 ) )
	{
		err = _ParseModeResource( &gInitialModeChangesRaw.screen, inArg );
		require_noerr_quiet( err, exit );
	}
	else if( strnicmpx( token, len, kAirPlayResourceIDString_MainAudio ) == 0 )
	{
		err = _ParseModeResource( &gInitialModeChangesRaw.mainAudio, inArg );
		require_noerr_quiet( err, exit );
	}
	else if( strnicmpx( token, len, kAirPlayAppStateIDString_PhoneCall ) == 0 )
	{
		gInitialModeChangesRaw.phoneCall = kAirPlayTriState_True;
	}
	else if( strnicmpx( token, len, kAirPlayAppStateIDString_Speech ) == 0 )
	{
		if( stricmp( inArg, kAirPlaySpeechModeString_None ) == 0 )
		{
			gInitialModeChangesRaw.speech = kAirPlaySpeechMode_None;
		}
		else if( stricmp( inArg, kAirPlaySpeechModeString_Speaking ) == 0 )
		{
			gInitialModeChangesRaw.speech = kAirPlaySpeechMode_Speaking;
		}
		else if( stricmp( inArg, kAirPlaySpeechModeString_Recognizing ) == 0 )
		{
			gInitialModeChangesRaw.speech = kAirPlaySpeechMode_Recognizing;
		}
		else
		{
			err = kParamErr;
			goto exit;
		}
	}
	else if( strnicmpx( token, len, kAirPlayAppStateIDString_TurnByTurn ) == 0 )
	{
		gInitialModeChangesRaw.turnByTurn = kAirPlayTriState_True;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}
	err = kNoErr;

exit:
	if( err ) fprintf( stderr, "error: bad mode '%s'\n", inArg );
	return( err );
}

//===========================================================================================================================
//	_ParseModeResource
//===========================================================================================================================

static OSStatus	_ParseModeResource( AirPlayResourceChange *inResource, const char *inArg )
{
	OSStatus			err;
	const char *		token;
	size_t				len;

	inResource->type		= kAirPlayTransferType_Take;
	inResource->priority	= kAirPlayTransferPriority_UserInitiated;

	// borrowID

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( strnicmpx( token, len, kAirPlayBorrowID_NativeVR ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_NativeVR;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_BackupCamera ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_BackupCamera;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_PhoneCall ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_PhoneCall;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_UINotification ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_UINotification;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_Standby ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_Standby;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_DisplayOff ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_DisplayOff;
	}
	else if( strnicmpx( token, len, kAirPlayBorrowID_AudioOff ) == 0 )
	{
		inResource->borrowID = kAirPlayBorrowID_AudioOff;
	}
	else
	{
		inResource->borrowID = NULL;
	}

	// type

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( strnicmpx( token, len, kAirPlayTransferTypeString_NotApplicable ) == 0 )
	{
		inResource->type = kAirPlayTransferType_NotApplicable;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Take ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Take;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Untake ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Untake;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Borrow ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Borrow;
	}
	else if( strnicmpx( token, len, kAirPlayTransferTypeString_Unborrow ) == 0 )
	{
		inResource->type = kAirPlayTransferType_Unborrow;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}

	// priority

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if( strnicmpx( token, len, kAirPlayTransferPriorityString_NotApplicable ) == 0 )
	{
		inResource->priority = kAirPlayTransferPriority_NotApplicable;
	}
	else if( strnicmpx( token, len, kAirPlayTransferPriorityString_NiceToHave ) == 0 )
	{
		inResource->priority = kAirPlayTransferPriority_NiceToHave;
	}
	else if( strnicmpx( token, len, kAirPlayTransferPriorityString_UserInitiated ) == 0 )
	{
		inResource->priority = kAirPlayTransferPriority_UserInitiated;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}

	// TakeConstraint

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if(      strnicmpx( token, len, kAirPlayConstraintString_Anytime ) == 0 )
	{
		inResource->takeConstraint = kAirPlayConstraint_Anytime;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_UserInitiated ) == 0 )
	{
		inResource->takeConstraint = kAirPlayConstraint_UserInitiated;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_Never ) == 0 )
	{
		inResource->takeConstraint = kAirPlayConstraint_Never;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}

	// BorrowConstraint

	token = inArg;
	while( ( *inArg != '\0' ) && ( *inArg != ',' ) ) ++inArg;
	len = (size_t)( inArg - token );
	if( *inArg != '\0' ) ++inArg;
	if(      strnicmpx( token, len, kAirPlayConstraintString_Anytime ) == 0 )
	{
		inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_UserInitiated ) == 0 )
	{
		inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_UserInitiated;
	}
	else if( strnicmpx( token, len, kAirPlayConstraintString_Never ) == 0 )
	{
		inResource->borrowOrUnborrowConstraint = kAirPlayConstraint_Never;
	}
	else
	{
		err = kParamErr;
		goto exit;
	}

	err = kNoErr;

exit:
	return( err );
}

//===========================================================================================================================
//	_initialModesCommand
//===========================================================================================================================

static void _initialModesCommand( const char * inCommand )
{
	char * 				offset = NULL;
	OSStatus			err;

	if( gAirPlaySession )
	{
		app_ulog_err( kLogLevelNotice, "AirPlaySession has already been started.\n" );
		return;
	}

	offset = strnstr( inCommand, "--night", strlen( inCommand ) );
	if( offset )	gNightMode = kCFBooleanTrue;

	offset = strnstr( inCommand, "screen", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "mainAudio", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "phoneCall", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "speech", strlen( inCommand ) );
	_ParseModes( offset );

	offset = strnstr( inCommand, "turnByTurn", strlen( inCommand ) );
	_ParseModes( offset );

	gInitialModes = AirPlayCreateModesDictionary( &gInitialModeChangesRaw, NULL, &err );
	check_noerr( err );

}

//===========================================================================================================================
//	_sendInitialChangeModeRequest
//===========================================================================================================================

static void	_sendInitialChangeModeRequest(
	const char *				inScreenBorrowID,
	AirPlayTransferType 		inScreenType,
	AirPlayTransferPriority		inScreenPriority,
	AirPlayConstraint			inScreenTake,
	AirPlayConstraint			inScreenBorrow,
	const char *				inAudioBorrowID,
	AirPlayTransferType 		inAudioType,
	AirPlayTransferPriority		inAudioPriority,
	AirPlayConstraint			inAudioTake,
	AirPlayConstraint			inAudioBorrow,
	AirPlayTriState				inPhone,
	AirPlaySpeechMode			inSpeech,
	AirPlayTriState				inTurnByTurn,
	AirPlayEntity				inScreenPermanentEntity,
	AirPlayConstraint			inScreenPermanentTakeConstraint,
	AirPlayConstraint			inScreenPermanentBorrowConstraint,
	AirPlayEntity				inAudioPermanentEntity,
	AirPlayConstraint			inAudioPermanentTakeConstraint,
	AirPlayConstraint			inAudioPermanentBorrowConstraint )
{
	// before the session is active, set initial modes.

	app_ulog( kLogLevelNotice, "\nInitial Mode (car -> iOS):"
		"\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
		"\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
		"\n - Phone: %d, Speech: %d, TurnByTurn: %d"
		"\n - ScreenPermanentEntity: %d, ScreenPermanentTakeConstraint: %d, ScreenPermanentBorrowConstraint: %d"
		"\n - AudioPermanentEntity : %d, AudioPermanentTakeConstraint : %d, AudioPermanentBorrowConstraint : %d.\n",
		inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
		inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn,
		inScreenPermanentEntity, inScreenPermanentTakeConstraint, inScreenPermanentBorrowConstraint,
		inAudioPermanentEntity, inAudioPermanentTakeConstraint, inAudioPermanentBorrowConstraint );

	// Initial Mode (car -> iOS)

	_setChangeModesStruct( &gInitialModeChangesRaw, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
		inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn );

	gInitialPermanentEntity.permanentEntityScreen = inScreenPermanentEntity;
	gInitialPermanentEntity.screen.takeConstraint = inScreenPermanentTakeConstraint;
	gInitialPermanentEntity.screen.borrowOrUnborrowConstraint = inScreenPermanentBorrowConstraint;

	gInitialPermanentEntity.permanentEntityMainAudio = inAudioPermanentEntity;
	gInitialPermanentEntity.mainAudio.takeConstraint = inAudioPermanentTakeConstraint;
	gInitialPermanentEntity.mainAudio.borrowOrUnborrowConstraint = inAudioPermanentBorrowConstraint;

	gInitialModes = AirPlayCreateInitialModesDictionary( &gInitialPermanentEntity, &gInitialModeChangesRaw, NULL, NULL );
}

//===========================================================================================================================
//	_sendGenericChangeModeRequest
//===========================================================================================================================

static void	_sendGenericChangeModeRequest(
	const char *				inScreenBorrowID,
	AirPlayTransferType 		inScreenType,
	AirPlayTransferPriority		inScreenPriority,
	AirPlayConstraint			inScreenTake,
	AirPlayConstraint			inScreenBorrow,
	const char *				inAudioBorrowID,
	AirPlayTransferType 		inAudioType,
	AirPlayTransferPriority		inAudioPriority,
	AirPlayConstraint			inAudioTake,
	AirPlayConstraint			inAudioBorrow,
	AirPlayTriState				inPhone,
	AirPlaySpeechMode			inSpeech,
	AirPlayTriState				inTurnByTurn )
{
	AirPlayModeChanges		changes;

	//when the Airplay session is active
	if( gAirPlaySession)
	{
		app_ulog( kLogLevelNotice, "\nChange Mode (car -> iOS):"
			"\n - ScreenBorrowID: %s, ScreenType: %d, ScreenPriority: %d, ScreenTake: %d, ScreenBorrow: %d"
			"\n - AudioBorrowID : %s, AudioType : %d, AudioPriority : %d, AudioTake : %d, AudioBorrow : %d"
			"\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
			inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn  );
		// changeModes (car -> iOS)
		_setChangeModesStruct( &changes, inScreenBorrowID, inScreenType, inScreenPriority, inScreenTake, inScreenBorrow,
			inAudioBorrowID, inAudioType, inAudioPriority, inAudioTake, inAudioBorrow, inPhone, inSpeech, inTurnByTurn );
		AirPlayReceiverSessionChangeModes( gAirPlaySession, &changes, NULL, NULL, NULL );
	}

}

//===========================================================================================================================
//	_setChangeModesStruct
//===========================================================================================================================

static void	_setChangeModesStruct(
	AirPlayModeChanges	* 		outModeChanges,
	const char *				inScreenBorrowID,
	AirPlayTransferType 		inScreenType,
	AirPlayTransferPriority		inScreenPriority,
	AirPlayConstraint			inScreenTake,
	AirPlayConstraint			inScreenBorrow,
	const char *				inAudioBorrowID,
	AirPlayTransferType 		inAudioType,
	AirPlayTransferPriority		inAudioPriority,
	AirPlayConstraint			inAudioTake,
	AirPlayConstraint			inAudioBorrow,
	AirPlayTriState				inPhone,
	AirPlaySpeechMode			inSpeech,
	AirPlayTriState				inTurnByTurn )
{
	if( !outModeChanges ) return;

	outModeChanges->screen.borrowID							= inScreenBorrowID;
	outModeChanges->screen.type								= inScreenType;
	outModeChanges->screen.priority							= inScreenPriority;
	outModeChanges->screen.takeConstraint 					= inScreenTake;
	outModeChanges->screen.borrowOrUnborrowConstraint 		= inScreenBorrow;
	outModeChanges->mainAudio.borrowID						= inAudioBorrowID;
	outModeChanges->mainAudio.type							= inAudioType;
	outModeChanges->mainAudio.priority						= inAudioPriority;
	outModeChanges->mainAudio.takeConstraint 				= inAudioTake;
	outModeChanges->mainAudio.borrowOrUnborrowConstraint 	= inAudioBorrow;
	outModeChanges->phoneCall 								= inPhone;
	outModeChanges->speech 									= inSpeech;
	outModeChanges->turnByTurn 								= inTurnByTurn;
}

//===========================================================================================================================
//	_changeResourceMode
//===========================================================================================================================

static void _changeResourceMode(
		const char *								inBorrowID,
		AirPlayResourceID							inResourceID,
		AirPlayTransferType							inType,
		AirPlayTransferPriority						inPriority,
		AirPlayConstraint							inTakeConstraint,
		AirPlayConstraint							inBorrowOrUnborrowConstraint)
{
	OSStatus 	err;
	CFStringRef borrowID = NULL;

	app_ulog( kLogLevelNotice, "\nChange Resource Mode (car -> iOS):"
		"\n - ResourceID: %d, BorrowID: %s, Type: %d, Priority: %d, TakeConstraint: %d, BorrowOrUnborrowConstraint: %d\n",
		inResourceID, inBorrowID, inType, inPriority, inTakeConstraint, inBorrowOrUnborrowConstraint );

	borrowID = CFStringCreateWithCString( kCFAllocatorDefault, inBorrowID, kCFStringEncodingUTF8 );
	require_action( borrowID, exit, err = kNoMemoryErr );

	err = AirPlayReceiverSessionChangeResourceMode( gAirPlaySession, inResourceID, inType,
				inPriority, inTakeConstraint, inBorrowOrUnborrowConstraint, borrowID, NULL, NULL, NULL );
	require_noerr( err, exit );

exit:
	CFReleaseNullSafe( borrowID );
}

//===========================================================================================================================
//	_changeAppState
//===========================================================================================================================

static void _changeAppState(
		AirPlaySpeechMode							inSpeechMode,
		AirPlayTriState								inPhoneCall,
		AirPlayTriState								inTurnByTurn)
{
	app_ulog( kLogLevelNotice, "\nChange App Mode (car -> iOS):"
		"\n - Phone: %d, Speech: %d, TurnByTurn: %d.\n", inPhoneCall, inSpeechMode, inTurnByTurn );

	if (gAirPlaySession)	AirPlayReceiverSessionChangeAppState( gAirPlaySession, inSpeechMode, inPhoneCall, inTurnByTurn, NULL, NULL, NULL );
}

//===========================================================================================================================
//	_requestUI
//===========================================================================================================================

static void _requestUI()
{
	app_ulog( kLogLevelNotice, "requestUI.\n" );

	if (gAirPlaySession)	AirPlayReceiverSessionRequestUI( gAirPlaySession, NULL, NULL, NULL );
}

static void _requestUIWithURL( const char * url )
{
    app_ulog( kLogLevelNotice, "requestUI with URL %s\n", url );

    if (gAirPlaySession)	AirPlayReceiverSessionRequestUI( gAirPlaySession, CFStringCreateWithCString( kCFAllocatorDefault, url, kCFStringEncodingUTF8 ), NULL, NULL );
}

//===========================================================================================================================
//	_requestSiriAction
//===========================================================================================================================

static void _requestSiriAction( AirPlaySiriAction	inAction )
{
	app_ulog( kLogLevelNotice, "requestSiriAction Action: %d\n", inAction );

	if (gAirPlaySession)	AirPlayReceiverSessionRequestSiriAction( gAirPlaySession, inAction, NULL, NULL );
}

//===========================================================================================================================
//	_setNightMode
//===========================================================================================================================

static void _setNightMode(int mode)
{
	app_ulog( kLogLevelNotice, "set night mode: %d.\n", mode );

	if( mode == 1 )	gNightMode = kCFLBooleanTrue;
	if( mode == 0 )	gNightMode = kCFLBooleanFalse;

	if (gAirPlaySession)	AirPlayReceiverSessionSetNightMode( gAirPlaySession, mode, NULL, NULL );
}

//===========================================================================================================================
//	_sendUIAppearanceUpdate
//===========================================================================================================================

static void _UIAppearanceUpdate( int inMode, int inSetting )
{
	app_ulog( kLogLevelNotice, "sends an UI appearance update mode: %d, setting: %d.\n", inMode, inSetting );

	gAppearanceMode = inMode;

	if( inSetting )	gAppearanceSetting = kAirPlayAppearanceSetting_Automatic;
	else			gAppearanceSetting = kAirPlayAppearanceSetting_UserChoice;

	if( gAirPlaySession )	AirPlayReceiverSessionUIAppearanceUpdate( gAirPlaySession, kMainScreenUUID, inMode, gAppearanceSetting, NULL, NULL );
}

//===========================================================================================================================
//	_setLimitedUI
//===========================================================================================================================

static void _setLimitedUI( int inLimitUI )
{
	app_ulog( kLogLevelNotice, "set limited UI: %d.\n", inLimitUI );

	if( inLimitUI == 1 )	gLimitedUI = kCFLBooleanTrue;
	if( inLimitUI == 0 )	gLimitedUI = kCFLBooleanFalse;

	if (gAirPlaySession)	AirPlayReceiverSessionSetLimitedUI( gAirPlaySession, inLimitUI, NULL, NULL );
}

//===========================================================================================================================
//	_sendETCUpdate
//===========================================================================================================================

static OSStatus _sendETCUpdate(bool inETCEnabled)
{
	OSStatus err;
	CFMutableDictionaryRef etcDict = NULL;
	CFMutableDictionaryRef vehicleDict = NULL;

	etcDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	require_action( etcDict, exit, err = kNoMemoryErr );
	CFDictionarySetBoolean( etcDict, CFSTR( kAirPlayKey_Active), inETCEnabled );

	vehicleDict = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
	require_action( vehicleDict, exit, err = kNoMemoryErr );
	CFDictionarySetValue( vehicleDict, CFSTR( kAirPlayVehicleInformation_ETC), etcDict );

	err = AirPlayReceiverSessionUpdateVehicleInformation( gAirPlaySession, vehicleDict, NULL, NULL );
	require_noerr( err, exit );

exit:
	CFReleaseNullSafe( etcDict );
	CFReleaseNullSafe( vehicleDict );
	return( err );
}

//===========================================================================================================================
//	_getAudioFormats
//===========================================================================================================================

static CFArrayRef _getAudioFormats( OSStatus *outErr )
{
	CFArrayRef					dictArray = NULL;
	OSStatus					err;
	AirPlayAudioFormat			inputFormats, outputFormats;

	// Main Audio - Compatibility
	inputFormats =
		kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono;
	outputFormats =
		kAirPlayAudioFormat_PCM_8KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		// kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Main Audio - Alert
	inputFormats = 0;
	outputFormats =
		//kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo |
		//kAirPlayAudioFormat_OPUS_48KHz_Mono |
		//kAirPlayAudioFormat_AAC_ELD_48KHz_Mono |
		//kAirPlayAudioFormat_AAC_ELD_44KHz_Mono |
		//kAirPlayAudioFormat_AAC_ELD_48KHz_Stereo;
		kAirPlayAudioFormat_AAC_ELD_44KHz_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Alert, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Main Audio - Default
	inputFormats =
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
		//kAirPlayAudioFormat_OPUS_24KHz_Mono |
		//kAirPlayAudioFormat_OPUS_16KHz_Mono;
		kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
	outputFormats =
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
		//kAirPlayAudioFormat_OPUS_24KHz_Mono |
		//kAirPlayAudioFormat_OPUS_16KHz_Mono;
		kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Main Audio - Media
	inputFormats = 0;
	outputFormats =
		//kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Media, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Main Audio - Telephony
	inputFormats =
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
		//kAirPlayAudioFormat_PCM_32KHz_16Bit_Mono |
		//kAirPlayAudioFormat_OPUS_16KHz_Mono |
		//kAirPlayAudioFormat_OPUS_24KHz_Mono |
		//kAirPlayAudioFormat_OPUS_48KHz_Mono;
		kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
		//kAirPlayAudioFormat_AAC_ELD_32KHz_Mono;
	outputFormats =
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono |
		//kAirPlayAudioFormat_PCM_32KHz_16Bit_Mono |
		//kAirPlayAudioFormat_OPUS_16KHz_Mono |
		//kAirPlayAudioFormat_OPUS_24KHz_Mono |
		//kAirPlayAudioFormat_OPUS_48KHz_Mono;
		kAirPlayAudioFormat_AAC_ELD_16KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
		//kAirPlayAudioFormat_AAC_ELD_32KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Main Audio - SpeechRecognition
	inputFormats =
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		//kAirPlayAudioFormat_OPUS_24KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
	outputFormats =
		kAirPlayAudioFormat_PCM_24KHz_16Bit_Mono |
		//kAirPlayAudioFormat_OPUS_24KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_24KHz_Mono;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Alt Audio - Default
	inputFormats = 0;
	outputFormats =
		//kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo |
		//kAirPlayAudioFormat_OPUS_48KHz_Mono |
		//kAirPlayAudioFormat_AAC_ELD_48KHz_Mono |
		//kAirPlayAudioFormat_AAC_ELD_44KHz_Mono |
		kAirPlayAudioFormat_AAC_ELD_48KHz_Stereo;
		//kAirPlayAudioFormat_AAC_ELD_44KHz_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Default, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Alt Audio - Compatibility
	inputFormats = 0;
	outputFormats =
		// kAirPlayAudioFormat_PCM_44KHz_16Bit_Stereo |
		kAirPlayAudioFormat_PCM_48KHz_16Bit_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Compatibility, inputFormats, outputFormats );
	require_noerr( err, exit );

	// Main High - Media
	inputFormats = 0;
	outputFormats =
		kAirPlayAudioFormat_AAC_LC_48KHz_Stereo;
		// kAirPlayAudioFormat_AAC_LC_44KHz_Stereo;
	err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_MainHighAudio, kAudioStreamAudioType_Media, inputFormats, outputFormats );
	require_noerr( err, exit );

	if( gSupportsEnhancedSiri )
	{
		// Aux Out - speechRecognition
		inputFormats = 0;
		outputFormats =
			kAirPlayAudioFormat_PCM_48KHz_16Bit_Mono;
			//kAirPlayAudioFormat_OPUS_48KHz_Mono;
		err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AuxOutAudio, kAudioStreamAudioType_SpeechRecognition, inputFormats, outputFormats );
		require_noerr( err, exit );

		// Aux In - speechRecognition
		outputFormats = 0;
		inputFormats =
			kAirPlayAudioFormat_PCM_16KHz_16Bit_Mono;
			//kAirPlayAudioFormat_OPUS_16KHz_Mono;
		err = AirPlayInfoArrayAddAudioFormat( &dictArray, kAudioStreamType_AuxInAudio, kAudioStreamAudioType_SpeechRecognition, inputFormats, outputFormats );
		require_noerr( err, exit );
	}

exit:
	if( outErr ) *outErr = err;
	return( dictArray );
}

//===========================================================================================================================
//	_getAudioLatencies
//===========================================================================================================================

static CFArrayRef _getAudioLatencies( OSStatus *outErr )
{
	CFArrayRef							dictArray = NULL;
	OSStatus							err;

	// $$$ TODO: obtain audio latencies for all audio formats and audio types supported by the underlying hardware.
	// Audio latencies are reported as an ordered array of dictionaries (from least restrictive to the most restrictive).
	// Each dictionary contains the following keys:
	//		[kAudioSessionKey_Type] - if not specified, then latencies are good for all stream types
	//		[kAudioSessionKey_AudioType] - if not specified, then latencies are good for all audio types
	//		[kAudioSessionKey_SampleRate] - if not specified, then latencies are good for all sample rates
	//		[kAudioSessionKey_SampleSize] - if not specified, then latencies are good for all sample sizes
	//		[kAudioSessionKey_Channels] - if not specified, then latencies are good for all channel counts
	//		[kAudioSessionKey_CompressionType] - if not specified, then latencies are good for all compression types
	//		kAudioSessionKey_InputLatencyMicros
	//		kAudioSessionKey_OutputLatencyMicros

	// MainAudio catch all - set 0 latencies for now - $$$ TODO set real latencies
	// err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, NULL, 0, 0, 0, 0, 0 );
	// require_noerr( err, exit );

	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, 8000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, 16000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, 24000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Compatibility, 48000, 16, 2, 0, 40000 );
    require_noerr( err, exit );

	// MainAudio default latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, 16000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Default, 24000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

	// MainAudio media latencies - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Media, 48000, 16, 2, 0, 40000 );
	require_noerr( err, exit );

	// MainAudio telephony latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, 16000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

    // MainAudio telephony latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Telephony, 24000, 16, 1, 160000, 40000 );
    require_noerr( err, exit );

	// MainAudio SpeechRecognition latencies - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_SpeechRecognition, 24000, 16, 1, 160000, 40000 );
	require_noerr( err, exit );

	// Main Audio alert latencies - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainAudio, kAudioStreamAudioType_Alert, 48000, 16, 2, 0, 40000 );
	require_noerr( err, exit );

	// AltAudio catch all latencies - set 0 latencies for now - $$$ TODO set real latencies
	// err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_AltAudio, NULL, 0, 0, 0, 0, 0 );
	// require_noerr( err, exit );

	// AltAudio default latencies - set 0 latencies for now - $$$ TODO set real latencies
    err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Default, 48000, 16, 2, 0, 40000 );
    require_noerr( err, exit );

	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_AltAudio, kAudioStreamAudioType_Compatibility, 48000, 16, 2, 0, 40000 );
    require_noerr( err, exit );

	// MainHighAudio Media latencies (wireless only) - set 0 latencies for now - $$$ TODO set real latencies
	err = AirPlayInfoArrayAddAudioLatency( &dictArray, kAudioStreamType_MainHighAudio, kAudioStreamAudioType_Media, 48000, 16, 2, 0, 40000 );
	require_noerr( err, exit );

	// $$$ TODO add more latencies dictionaries as needed

exit:
	if( outErr ) *outErr = kNoErr;
	return( dictArray );
}

//===========================================================================================================================
//	GetHIDDevices
//===========================================================================================================================

static CFArrayRef _getHIDDevices( OSStatus *outErr )
{
	CFArrayRef					dictArray = NULL;
	OSStatus					err;

	if( gHiFiTouch || gLoFiTouch )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDTouchScreenSingleCreateDescriptor( &descPtr, &descLen, gVideoWidth, gVideoHeight );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gTouchUID, "Touch Screen", kUSBVendorTouchScreen, kUSBProductTouchScreen, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if( gKnob )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDKnobCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gKnobUID, "Knob & Buttons", kUSBVendorKnobButtons, kUSBProductKnobButtons, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if( gProxSensor )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDProximityCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gProxSensorUID, "Proximity Sensor", kUSBVendorProxSensor, kUSBProductProxSensor, kUSBCountryCodeUnused, descPtr, descLen, kMainScreenUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if ( gTelephony )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDTelephonyCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gTelephonyUID, "Telephony Key", kUSBVendorTelephony, kUSBProductTelephony, kUSBCountryCodeUS, descPtr, descLen, kDefaultUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	if ( gMediaButtons )
	{
		uint8_t *	descPtr;
		size_t		descLen;

		err = HIDMediaButtonsCreateDescriptor( &descPtr, &descLen );
		require_noerr( err, exit );

		err = AirPlayInfoArrayAddHIDDevice( &dictArray, gMediaButtonsUID, "Media Buttons", kUSBVendorMediaButtons, kUSBProductMediaButtons, kUSBCountryCodeUS, descPtr, descLen, kDefaultUUID );
		free( descPtr );
		require_noerr( err, exit );
	}

	err = kNoErr;

exit:
	if( outErr ) *outErr = err;
	return( dictArray );
}

//===========================================================================================================================
//	_getScreenDisplays
//===========================================================================================================================

static CFArrayRef _getScreenDisplays( OSStatus *outErr )
{
	OSStatus err;

	uint32_t mainWidthPixels = gVideoWidth;
	uint32_t mainHeightPixels = gVideoHeight;
	uint32_t mainWidthMM = gWidthPhysical;
	uint32_t mainHeightMM = gHeightPhysical;
	uint32_t mainFPS = 30;

	uint32_t altWidthPixels = 800;
	uint32_t altHeightPixels = 480;
	uint32_t altWidthMM = 175;
	uint32_t altHeightMM = 105;
	uint32_t altFPS = 30;

	CFArrayRef dictArray = NULL;
	CFMutableDictionaryRef screenDict = NULL;
	uint32_t u32 = 0;

	uint32_t viewAreaCount = 4;
	uint32_t initialViewArea = 0;
	AirPlayScreenArea activeAreas[4];
	uint32_t adjacentAreas[2];
	uint32_t adjacentAreaCount;

	if( gKnob )			u32 |= kAirPlayDisplayFeatures_Knobs;
	if( gHiFiTouch )	u32 |= kAirPlayDisplayFeatures_HighFidelityTouch;
	if( gLoFiTouch )	u32 |= kAirPlayDisplayFeatures_LowFidelityTouch;
	if( gTouchpad )		u32 |= kAirPlayDisplayFeatures_Touchpad;

	// Full screen 1920x720 @ (0x0)
	activeAreas[0].width = 1920;
	activeAreas[0].height = 720;
	activeAreas[0].originX = 0;
	activeAreas[0].originY = 0;
	activeAreas[0].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[0].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[0].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[0].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Left screen 1400x720 @ (0,0)
	activeAreas[1].width = 1400;
	activeAreas[1].height = 720;
	activeAreas[1].originX = 0;
	activeAreas[1].originY = 0;
	activeAreas[1].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[1].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[1].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[1].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Right screen 1400x720 @ (520,0)
	activeAreas[2].width = 1400;
	activeAreas[2].height = 720;
	activeAreas[2].originX = 520;
	activeAreas[2].originY = 0;
	activeAreas[2].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[2].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[2].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[2].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Middle screen 1400x600 @ (140,60)
	activeAreas[3].width = 1400;
	activeAreas[3].height = 600;
	activeAreas[3].originX = 260;
	activeAreas[3].originY = 60;
	activeAreas[3].mainScreenProperties.viewAreaTransitionControl = false;
	activeAreas[3].mainScreenProperties.viewAreaSupportsFocusTransfer = false;
	activeAreas[3].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Undefined;
	activeAreas[3].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Initial view area = activeArea[0]
	initialViewArea = 0;

	// Possible transitions to/from the initial view area activeArea[0]
	// activeArea[0] <-> activeArea[1]
	// activeArea[0] <-> activeArea[3]
	adjacentAreas[0] = 1;
	adjacentAreas[1] = 3;
	adjacentAreaCount = 2;

	// Create Main Screen Dictionary
	err = AirPlayMainScreenDictCreate( &screenDict, kMainScreenUUID, u32, gPrimaryInputDevice, mainFPS, mainWidthPixels, mainHeightPixels, mainWidthMM, mainHeightMM );
	require_noerr( err, exit );

	// Add View Areas
	// err = AirPlayScreenDictSetViewAreas( screenDict, initialViewArea, adjacentAreaCount, adjacentAreas, viewAreaCount, activeAreas, NULL );
	// require_noerr( err, exit );

	// Add Appearance options
	err = AirPlayScreenDictAddUIAppearance( screenDict, gAppearanceMode, gAppearanceSetting );
	require_noerr( err, exit );

	// err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Light, kAirPlayAppearanceSetting_Automatic );
	// require_noerr( err, exit );

	err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	require_noerr( err, exit );
	CFRelease( screenDict );
	screenDict = NULL;

	// Instrument Cluster (show the map upon connect)
	// err = AirPlayAltScreenDictCreate( &screenDict, kAltScreenUUID, altFPS, altWidthPixels, altHeightPixels, altWidthMM, altHeightMM, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterMap ) );
	// require_noerr( err, exit );

	// Add Appearance option
	// err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Dark, kAirPlayAppearanceSetting_Always );
	// require_noerr( err, exit );

	// err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	// require_noerr( err, exit );
	// CFRelease( screenDict );
	// screenDict = NULL;

	// Instrument Cluster (show the turn card upon connect)
	// err = AirPlayAltScreenDictCreate( &screenDict, kAltScreen2UUID, altFPS, altWidthPixels, altHeightPixels, altWidthMM, altHeightMM, CFSTR( kAirPlayKey_ScreenURLsInstrumentClusterInstructionCard ) );
	// require_noerr( err, exit );

	// Add Appearance option
	// err = AirPlayScreenDictAddMapAppearance( screenDict, kAirPlayAppearanceMode_Dark, kAirPlayAppearanceSetting_Always );
	// require_noerr( err, exit );

	// err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	// require_noerr( err, exit );
	// CFRelease( screenDict );
	// screenDict = NULL;

exit:
	*outErr = err;
	CFReleaseNullSafe( screenDict );
	return( dictArray );
}

static void viewAreaChangeFullToLeft()
{
	uint32_t viewAreaIndex = 1;
	uint32_t adjacentAreas[2] = { 0, 2 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

static void viewAreaChangeLeftToRight()
{
	uint32_t viewAreaIndex = 2;
	uint32_t adjacentAreas[2] = { 1, 3 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

static void viewAreaChangeRightToMiddle()
{
	uint32_t viewAreaIndex = 3;
	uint32_t adjacentAreas[2] = { 2, 0 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

static void viewAreaChangeMiddleToFull()
{
	uint32_t viewAreaIndex = 0;
	uint32_t adjacentAreas[2] = { 3, 1 };
	uint32_t adjacentAreaCount = 2;

	AirPlayReceiverSessionViewAreaUpdate( gAirPlaySession, kMainScreenUUID, 0, viewAreaIndex, adjacentAreaCount, adjacentAreas, NULL, NULL );
}

//===========================================================================================================================
//	_getScreenDisplaysWindowConfig
//===========================================================================================================================

static CFArrayRef _getScreenDisplaysWindowConfig( OSStatus *outErr )
{
	OSStatus err;

	uint32_t mainWidthPixels = 800;
	uint32_t mainHeightPixels = 1280;
	uint32_t mainWidthMM = 168;
	uint32_t mainHeightMM = 240;
	uint32_t mainFPS = 30;

	CFArrayRef dictArray = NULL;
	CFMutableDictionaryRef screenDict = NULL;
	uint32_t u32 = 0;

	uint32_t viewAreaCount = 2;
	uint32_t initialViewArea = 0;
	AirPlayScreenArea activeAreas[2];
	uint32_t adjacentAreas[1];
	uint32_t adjacentAreaCount;

	if( gKnob )			u32 |= kAirPlayDisplayFeatures_Knobs;
	if( gHiFiTouch )	u32 |= kAirPlayDisplayFeatures_HighFidelityTouch;
	if( gLoFiTouch )	u32 |= kAirPlayDisplayFeatures_LowFidelityTouch;
	if( gTouchpad )		u32 |= kAirPlayDisplayFeatures_Touchpad;

	// Full screen 800x1200 @ (0,80)
	activeAreas[0].width = 800;
	activeAreas[0].height = 1200;
	activeAreas[0].originX = 0;
	activeAreas[0].originY = 80;
	activeAreas[0].mainScreenProperties.viewAreaTransitionControl = true;
	activeAreas[0].mainScreenProperties.viewAreaSupportsFocusTransfer = true;
	activeAreas[0].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_Bottom;
	activeAreas[0].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Left screen 800x480 @ (0,80)
	activeAreas[1].width = 800;
	activeAreas[1].height = 480;
	activeAreas[1].originX = 0;
	activeAreas[1].originY = 80;
	activeAreas[1].mainScreenProperties.viewAreaTransitionControl = true;
	activeAreas[1].mainScreenProperties.viewAreaSupportsFocusTransfer = true;
	activeAreas[1].mainScreenProperties.viewAreaStatusBarEdge = kAirPlayStatusBarEdge_DriveSide;
	activeAreas[1].mainScreenProperties.drawUIOutsideSafeArea = false;

	// Initial view area = activeArea[0]
	initialViewArea = 0;

	// Possible transitions to/from the initial view area activeArea[0]
	// activeArea[0] <-> activeArea[1]
	adjacentAreaCount = 1;
	adjacentAreas[0] = 1;

	// Create Main Screen Dictionary
	err = AirPlayMainScreenDictCreate( &screenDict, kMainScreenUUID, u32, gPrimaryInputDevice, mainFPS, mainWidthPixels, mainHeightPixels, mainWidthMM, mainHeightMM );
	require_noerr( err, exit );

	// Add View Areas
	err = AirPlayScreenDictSetViewAreas( screenDict, initialViewArea, adjacentAreaCount, adjacentAreas, viewAreaCount, activeAreas, NULL );
	require_noerr( err, exit );

	// Support Corner Clipping Masks
	err = AirPlayScreenDictSetCornerMasksSupport( screenDict, true );
	require_noerr( err, exit );

	// Since this viewArea supports Focus Transfer, give controller focus at connect
	err = AirPlayScreenDictSetAccessoryGiveFocus( screenDict, false );

	err = AirPlayInfoArrayAddScreen( &dictArray, screenDict );
	require_noerr( err, exit );
	CFRelease( screenDict );
	screenDict = NULL;

exit:
	*outErr = err;
	CFReleaseNullSafe( screenDict );
	return( dictArray );
}

static CFDictionaryRef createInitialModesForBackupCamera()
{
	AirPlayModeChanges initialModes;
	AirPlayInitialPermanentEntity initialPerm;
	CFDictionaryRef dict;

	// Example for backup camera where the permanent entity is accessory
	AirPlayModeChangesInit( &initialModes );

	initialModes.speech = kAirPlaySpeechMode_None;
	initialModes.phoneCall = kAirPlayTriState_False;
	initialModes.turnByTurn = kAirPlayTriState_False;

	initialModes.screen.type = kAirPlayTransferType_Borrow;
	initialModes.screen.priority = kAirPlayTransferPriority_UserInitiated;
	initialModes.screen.takeConstraint = kAirPlayConstraint_NotApplicable;
	initialModes.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Never;
	initialModes.screen.borrowID = kAirPlayBorrowID_BackupCamera;

	initialModes.mainAudio.type = kAirPlayTransferType_Take;
	initialModes.mainAudio.priority = kAirPlayTransferPriority_UserInitiated;
	initialModes.mainAudio.takeConstraint = kAirPlayConstraint_UserInitiated;
	initialModes.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    AirPlayInitialPermanentEntityInit( &initialPerm );

	initialPerm.permanentEntityScreen = kAirPlayEntity_Accessory;
	initialPerm.screen.takeConstraint = kAirPlayConstraint_UserInitiated;
	initialPerm.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

	initialPerm.permanentEntityMainAudio = kAirPlayEntity_Accessory;
	initialPerm.mainAudio.takeConstraint = kAirPlayConstraint_UserInitiated;
	initialPerm.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

	dict = AirPlayCreateInitialModesDictionary( &initialPerm, &initialModes, CFSTR("Initial modes"), NULL);

	return dict;
}

static CFDictionaryRef createInitialModesForController( void )
{
	AirPlayModeChanges initialModes;
	AirPlayInitialPermanentEntity initialPerm;
	CFDictionaryRef dict;

	// Example for controller to have the resources
	AirPlayModeChangesInit( &initialModes );

	initialModes.speech = kAirPlaySpeechMode_None;
	initialModes.phoneCall = kAirPlayTriState_False;
	initialModes.turnByTurn = kAirPlayTriState_False;

	// For backwards compatability
	initialModes.screen.type = kAirPlayTransferType_Take;
	initialModes.screen.priority = kAirPlayTransferPriority_NiceToHave;
	initialModes.screen.takeConstraint = kAirPlayConstraint_Anytime;
	initialModes.screen.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

	initialModes.mainAudio.type = kAirPlayTransferType_Take;
	initialModes.mainAudio.priority = kAirPlayTransferPriority_NiceToHave;
	initialModes.mainAudio.takeConstraint = kAirPlayConstraint_Anytime;
	initialModes.mainAudio.borrowOrUnborrowConstraint = kAirPlayConstraint_Anytime;

    AirPlayInitialPermanentEntityInit( &initialPerm );

	initialPerm.permanentEntityScreen = kAirPlayEntity_Controller;
	initialPerm.permanentEntityMainAudio = kAirPlayEntity_Controller;

	dict = AirPlayCreateInitialModesDictionary( &initialPerm, &initialModes, CFSTR("Initial modes"), NULL);

	return dict;
}
int	DestroyAppleCarPlay(){
	memset( gDeviceId, 0, 18 );
	return 1;
}

int HidGadgetInit(){
	iAP2OverHidGadgetSetup();
	return 0;
}

int HidGadgetUninit(){
	iAP2OverHidGadgetTearDown();
	return 0;
}

int UsbGadgetInit(){
	iAP2OverCarPlaySetup();
	return 0;
}

int UsbGadgetUninit(){
	iAP2OverCarPlayTearDown();
	return 0;
}

int sendMediaLibraryInfo(struct MediaInfo *mediaInfo){
	//vid:0x19d9 pid:0x7140 Isuzu五十铃不能发这个,播放FWIPTV会crash
	uint16_t vid = 0x0000;
	uint16_t pid = 0x0000;
	int len = 2;
	gAppStubDelegate.requestAccessoryUsbVidPid_f(0,&vid,&pid,&len);
	//vid:0x108c pid:0x0103长城哈弗H6
	if((vid == 0x19d9 && pid == 0x7140)||(vid == 0x108c && pid == 0x0103))
	{

	}else{
		iAP2SendMediaLibraryInfo(mediaInfo);
	}
//	iAP2SendMediaLibraryInfo(mediaInfo);
	return 0;
}

int requestStartLocationInformation(int start){
	iAP2StartLocationInformation(start);
	return 0;
}

int requestSendDeviceTimeUpdate( ){
	iAP2SendDeviceTimeUpdate( );
	return 0;
}

int sendCallStateUpdateInfo(struct CallStateInfo *callStateInfo){
	iAP2SendCallStateUpdateInfo(callStateInfo);
	return 0;
}

int CarPlayAppStubAudioStart(int audioType,int delay,int sampleRate,int bits,int channels){
	app_ulog( kLogLevelWarning, "CarPlayAppStubAudioStart. \n");
	CarPlayControlClientAudioStart();

#if 0
	if( audioType == AUDIO_CHANNEL_PHONE || audioType == AUDIO_CHANNEL_SPEECH )
	{
		if( gAirPlayModeStateRaw.screen   != kAirPlayEntity_Controller )
		{
			gAppNeedReleaseScreen = 1;
			gAirPlayModeStateRaw.screen   = kAirPlayEntity_Controller;
			gAirPlayModeStateRaw.permScreen   = kAirPlayEntity_Accessory;

			//需要回复modesChanged
			gInitialModeChangesRaw.screen.type = kAirPlayTransferType_Untake;
			CarPlayControlClientModesChanged();
			CarPlayControlClientResumeScreen();
		}

	}
#endif
	return 0;
}

int CarPlayAppStubAudioStop(int audioType,int delay,int sampleRate,int bits,int channels){
	app_ulog( kLogLevelWarning, "CarPlayAppStubAudioStop. \n");
	CarPlayControlClientAudioStop();
	return 0;
}

//===========================================================================================================================
//	CarPlayAppStubSetDelegate
//===========================================================================================================================
void CarPlayAppStubSetDelegate( const CarPlayAppStubDelegate *inDelegate )
{
	gAppStubDelegate = *inDelegate;
}


void cacheKeyFrame(uint8_t *buf, uint64_t len ) {
	//00 00 00 01 05
	if( len >=5 )
	{
	    if (buf[0] == 0x00
	    		&&buf[1] == 0x00
	    		&&buf[2] == 0x00
	    		&&buf[3] == 0x01
	    		&&((buf[4]&0x1F) == 5)) {//I Frame
	    	app_ulog( kLogLevelWarning, "Video keyFrame catch. \n");

	    	if( videoKeyFrame )
	    	{
	    		free( videoKeyFrame );
	    		videoKeyFrame = NULL;
	    	}
	    	videoKeyFrameLen = len;
	    	videoKeyFrame =(uint8_t *) malloc(videoKeyFrameLen);
	    	memset(videoKeyFrame, 0, videoKeyFrameLen);
	    	memcpy(videoKeyFrame, buf, videoKeyFrameLen);
	    }
	}


}

//===========================================================================================================================
//	CarPlayAppStubSendVideo
//===========================================================================================================================
int CarPlayAppStubSendVideo(void* data,int length)
{
//	cacheKeyFrame((uint8_t *)data,length);
	ScreenStreamInputProcessData(0, data, length, 1, 0, NULL);
	return 0;
}

//===========================================================================================================================
//	CarPlayAppStubSendAudio
//===========================================================================================================================
int CarPlayAppStubSendAudio(void* data,int length,int frames)
{
//	frames 音频数据帧大小 frames = len / (bits / 8 * channels)
//	int frame = length /(16 / 8 * 2);
	AudioStreamInputProcessData(0, data, length, frames, UpTicks(), NULL);
	return 0;
}


//===========================================================================================================================
//	CarPlayAppStubReadAudio
//===========================================================================================================================
int CarPlayAppStubReadAudio(void* data,int length,int frames,uint64_t timestamp)
{
//	frames 音频数据帧大小 frames = len / (bits / 8 * channels)
//	int frame = length /(16 / 8 * 1);
	AudioStreamOutputProcessData(0, data, length, frames, timestamp, NULL);
	return 0;
}


#define AUDIO_CHANNEL_STOP 			0
#define AUDIO_CHANNEL_MEDIA 		1
#define AUDIO_CHANNEL_PHONE 		2
#define AUDIO_CHANNEL_SPEECH 		3
#define AUDIO_CHANNEL_NAVIGATION 	4

int CarPlayAppStubRequestAudioFocus(int audioChannel,int callState)
{

	if(AUDIO_CHANNEL_MEDIA == audioChannel)
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->Media.\n" ,audioChannel);
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_NotApplicable;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlayEntity_NotApplicable;
		//3.TurnByTurn entity
		gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_NotApplicable;

		//resource
		gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Controller;
		gAirPlayModeStateRaw.permMainAudio = kAirPlayEntity_Controller;

		gAirPlayModeStateRaw.targetPlayChannel = audioChannel;
		CarPlayControlClientModesChangedAudio();

	}
	else if(AUDIO_CHANNEL_PHONE == audioChannel)
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->Phone.\n" ,audioChannel);
#if 1
		if( gAirPlayModeStateRaw.screen   != kAirPlayEntity_Controller )
		{
			gAppNeedReleaseScreen = 1;
			gAirPlayModeStateRaw.screen   = kAirPlayEntity_Controller;

			//需要回复modesChanged
			gInitialModeChangesRaw.screen.type = kAirPlayTransferType_Unborrow;
//			CarPlayControlClientModesChanged();
//			CarPlayControlClientResumeScreen();
		}
#endif
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_Controller;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_Speaking;
		if( callState == 2 )
		{
			gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_NotApplicable;
			gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		}
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlayEntity_Controller;
		//3.TurnByTurn entity
//			gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_Accessory;
		gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_NotApplicable;


		//resource
		gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Controller;

		gAirPlayModeStateRaw.targetPlayChannel = audioChannel;

		CarPlayControlClientModesChangedAudio();
		if( gAppNeedReleaseScreen ){CarPlayControlClientResumeScreen();}
	}
	else if(AUDIO_CHANNEL_SPEECH == audioChannel )
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->Speech.\n" ,audioChannel);
#if 1
		if( gAirPlayModeStateRaw.screen   != kAirPlayEntity_Controller )
		{
			gAppNeedReleaseScreen = 1;
			gAirPlayModeStateRaw.screen   = kAirPlayEntity_Controller;

			//需要回复modesChanged
			gInitialModeChangesRaw.screen.type = kAirPlayTransferType_Unborrow;
//			CarPlayControlClientModesChanged();
//			CarPlayControlClientResumeScreen();
		}
#endif
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_Controller;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_Speaking;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlayEntity_NotApplicable;
		//3.TurnByTurn entity
		gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_NotApplicable;

		//resource
		gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Controller;

		gAirPlayModeStateRaw.targetPlayChannel = audioChannel;
		CarPlayControlClientModesChangedAudio();
		if( gAppNeedReleaseScreen ){CarPlayControlClientResumeScreen();}
	}
	else if(AUDIO_CHANNEL_NAVIGATION == audioChannel)
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->TurnByTurn.\n" ,audioChannel);
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_NotApplicable;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlayEntity_NotApplicable;
		//3.TurnByTurn entity
		gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_Controller;

		//resource
//		gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_NotApplicable;
//		gAirPlayModeStateRaw.permMainAudio = kAirPlayEntity_NotApplicable;

		gAirPlayModeStateRaw.targetPlayChannel = audioChannel;
		CarPlayControlClientModesChangedAudio();
//		CarPlayControlClientDuckAudio();

	}

	return 0;
}


int CarPlayAppStubAbandonAudioFocus(int audioChannel)
{
	if(AUDIO_CHANNEL_MEDIA == audioChannel)
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->Media.\n" ,audioChannel);
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlaySpeechMode_NotApplicable;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		gAirPlayModeStateRaw.speech.entity = kAirPlaySpeechMode_NotApplicable;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlaySpeechMode_NotApplicable;
		//3.TurnByTurn entity
		gAirPlayModeStateRaw.turnByTurn = kAirPlaySpeechMode_NotApplicable;

		//resource
		if ( gAirPlayModeStateRaw.mainAudio == kAirPlayEntity_Controller)
		{
			gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Controller;
		}
		gAirPlayModeStateRaw.targetStopChannel = audioChannel;
		CarPlayControlClientTearDownAudio();

	}
	else if(AUDIO_CHANNEL_PHONE == audioChannel)
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->Phone.\n" ,audioChannel);
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlaySpeechMode_NotApplicable;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlayEntity_NotApplicable;
		//3.TurnByTurn entity
//			gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_Accessory;
		gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_NotApplicable;


		//resource
		if ( gAirPlayModeStateRaw.permMainAudio == kAirPlayEntity_Accessory)
		{
			gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Accessory;
		}

		gAirPlayModeStateRaw.targetStopChannel = audioChannel;

		CarPlayControlClientTearDownAudio();
	}
	else if(AUDIO_CHANNEL_SPEECH == audioChannel )
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->Speech.\n" ,audioChannel);
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlaySpeechMode_NotApplicable;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlaySpeechMode_NotApplicable;
		//3.TurnByTurn entity
		gAirPlayModeStateRaw.turnByTurn = kAirPlaySpeechMode_NotApplicable;

		//resource
		if ( gAirPlayModeStateRaw.permMainAudio == kAirPlayEntity_Accessory)
		{
			gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_Accessory;
		}
		gAirPlayModeStateRaw.targetStopChannel = audioChannel;
		CarPlayControlClientTearDownAudio();
	}
	else if(AUDIO_CHANNEL_NAVIGATION == audioChannel)
	{
		app_ulog( kLogLevelWarning, "AppStub audioChannel = %d ->TurnByTurn.\n" ,audioChannel);
		//AppState
		//1.speech
		gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_Accessory;
		gAirPlayModeStateRaw.speech.mode = kAirPlaySpeechMode_None;
		gAirPlayModeStateRaw.speech.entity = kAirPlayEntity_Accessory;
		//2.Phone
		gAirPlayModeStateRaw.phoneCall = kAirPlayEntity_NotApplicable;
		//3.TurnByTurn entity
		gAirPlayModeStateRaw.turnByTurn = kAirPlayEntity_Accessory;

		//resource
//		gAirPlayModeStateRaw.mainAudio = kAirPlayEntity_NotApplicable;
//		gAirPlayModeStateRaw.permMainAudio = kAirPlayEntity_NotApplicable;

		gAirPlayModeStateRaw.targetStopChannel = audioChannel;
		CarPlayControlClientUnduckAudio();
		CarPlayControlClientChangeModesCommandResponse();
		CarPlayControlClientTearDownAudio();

	}

	return 0;
}

//===========================================================================================================================
//	CarPlayAppStubBack2Home
//===========================================================================================================================
int CarPlayAppStubBack2Home() //返回原车
{
	app_ulog( kLogLevelWarning, "CarPlayAppStubBack2Home \n");
	CarPlayControlClientBack2Home();

	return 0;
}


